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 synccannot cross two endpoints in one command.--endpoint-urlis global to the invocation — there is no--source-endpoint-url— so a singlesyncbetween AWS and K3 would point both sides at the same host. Usercloneormc, which model each side as a named remote, or stage through local disk with aws-cli. The tabs below are ordered accordingly.
rclone
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-1The 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 --progressVerify 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:
| Pattern | Latency | Ops complexity |
|---|---|---|
mc mirror --watch | Seconds | Low — one long-running process |
Periodic rclone sync cron | Minutes / hours | Lowest — just a cron job |
| S3 event → Lambda → K3 PUT | Sub-second | Highest — 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
| Symptom | Cause | Fix |
|---|---|---|
403 AccessDenied reading from AWS | AWS credentials wrong / missing s3:GetObject | Verify with aws s3 ls s3://source --profile aws-prod first |
402 QuotaExceeded writing to K3 | Org storage entitlement exhausted, checked against Content-Length before a byte moves | Not retryable — raise the org quota |
403 AccessDenied writing to K3 | Signature mismatch, or the bucket belongs to another org | Confirm 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 list | It was created over raw S3 (mc mb / aws s3 mb) — a backend bucket with no K3 record, so no ACL, CORS, quota or pipelines | Recreate with dodil data bucket create |
| Files transfer but content-type is wrong | aws-cli re-infers from extension on the upload leg | Pre-set with --content-type per-file, or use mc mirror --preserve / rclone |
| Object versions / history lost | K3 doesn’t model versions | Flatten versioned objects to keys like path/v1.txt, path/v2.txt server-side before sync |
Multipart parts visible in K3 (?uploads) after sync | Interrupted upload | ListMultipartUploads then AbortMultipartUpload; bucket quota counts in-flight parts |
See also
- S3 Compatibility — what S3 features carry over vs not
- Multipart Large Files — what
aws s3 syncis doing under the hood for big files - Pipelines — what happens to each migrated object