Skip to Content
We are live but in Staging 🎉
Data EnginesObjectsRecipesMirror from AWS S3

Mirror from AWS S3

Goal: copy objects from an existing AWS S3 bucket (or any S3-compatible store) into K3 — either as a one-shot migration or as an ongoing sync.

Why: every object you mirror in becomes a K3 object — it shows up in dodil data object list, supports presigned URLs, and enters K3’s pipeline plane (ingest rules fire on every uploaded object, so a mirror can be the entry point to auto-indexing your S3 data).

Mirroring transfers bytes + content-type. It does not transfer S3-specific metadata that K3 doesn’t model: object versions, lifecycle state, replication status, S3 tags, per-object ACLs. See S3 Compatibility — Not supported.

One-shot migration

aws s3 sync cannot cross two endpoints in one command. --endpoint-url is global to the invocation — there is no --source-endpoint-url — so a single sync between AWS and K3 would point both sides at the same host. Use rclone or mc, which model each side as a named remote, or stage through local disk with aws-cli. The tabs below are ordered accordingly.

rclone is the best fit here — it names each side as its own remote with its own endpoint, parallelizes aggressively, and resumes cleanly.

# ~/.config/rclone/rclone.conf [aws-prod] type = s3 provider = AWS access_key_id = <AWS_KEY> secret_access_key = <AWS_SECRET> region = us-east-1 [dodil-k3] type = s3 provider = Other access_key_id = <access_key_id> secret_access_key = <secret_access_key> endpoint = https://object.uk-lon-1.dodil.io force_path_style = true region = us-east-1

The K3-side <access_key_id> / <secret_access_key> come from your Dodil API key — see S3 Compatibility for what goes in each field.

# Pre-create the K3 bucket on the admin plane — NOT `rclone mkdir` dodil data bucket create migrated-from-s3 -d "Mirrored from production AWS" # Dry run first — see what would copy rclone sync aws-prod:my-aws-bucket dodil-k3:migrated-from-s3 \ --transfers 16 --checkers 32 --dry-run # Real run with progress rclone sync aws-prod:my-aws-bucket dodil-k3:migrated-from-s3 \ --transfers 16 --checkers 32 --progress

Verify the migration

# Object counts echo "AWS:" && aws s3 ls s3://my-aws-bucket/ --recursive --profile aws-prod | wc -l echo "K3: " && dodil data object list -b migrated-from-s3 -o json | jq '.objects | length' # Spot-check a single key — sizes should match exactly aws s3api head-object --bucket my-aws-bucket --key path/to/object --profile aws-prod dodil data object show path/to/object -b migrated-from-s3 -o json # Byte-compare a small file diff <(aws s3 cp s3://my-aws-bucket/foo.json - --profile aws-prod) \ <(aws s3 cp s3://migrated-from-s3/foo.json - --endpoint-url https://object.uk-lon-1.dodil.io)

Ongoing replication

If you need K3 to stay in sync with an upstream S3 bucket continuously, you have three patterns:

PatternLatencyOps complexity
mc mirror --watchSecondsLow — one long-running process
Periodic rclone sync cronMinutes / hoursLowest — just a cron job
S3 event → Lambda → K3 PUTSub-secondHighest — Lambda + IAM

For most cases, a periodic rclone sync from a cron is the right answer — it’s idempotent, restartable, and stateless, and unlike aws s3 sync it can address both endpoints in one command.

What flows into K3’s pipeline plane

When aws s3 sync uploads each object, K3 treats it like any other PutObject — meaning ingest discovery fires on every transferred object. If you’ve set up pipeline rules on the destination bucket, your migration is also your first ingest run. See Pipelines for how to wire rules.

If you want to mirror cold without triggering ingest, mirror first to a bucket that has no rules, then apply the rules after the sync finishes and backfill explicitly with dodil data ingest trigger-discovery (dodil data ingest jobs and dodil data ingest watch <job-id> follow the run).

Common gotchas

SymptomCauseFix
403 AccessDenied reading from AWSAWS credentials wrong / missing s3:GetObjectVerify with aws s3 ls s3://source --profile aws-prod first
402 QuotaExceeded writing to K3Org storage entitlement exhausted, checked against Content-Length before a byte movesNot retryable — raise the org quota
403 AccessDenied writing to K3Signature mismatch, or the bucket belongs to another orgConfirm the SigV4 signing secret is the bound service account’s client secret, not the dk_ token’s secret half
Destination bucket exists on the wire but not in dodil data bucket listIt was created over raw S3 (mc mb / aws s3 mb) — a backend bucket with no K3 record, so no ACL, CORS, quota or pipelinesRecreate with dodil data bucket create
Files transfer but content-type is wrongaws-cli re-infers from extension on the upload legPre-set with --content-type per-file, or use mc mirror --preserve / rclone
Object versions / history lostK3 doesn’t model versionsFlatten versioned objects to keys like path/v1.txt, path/v2.txt server-side before sync
Multipart parts visible in K3 (?uploads) after syncInterrupted uploadListMultipartUploads then AbortMultipartUpload; bucket quota counts in-flight parts

See also