Skip to main content
This guide walks through setting up an Amazon ECR mirror cache for images from cr.root.io. Once configured, your workloads pull Root images from your own ECR - reducing external network dependency, enabling lifecycle policies, and keeping image access within your AWS account.

What you will achieve

  • A copy of selected cr.root.io images stored in your AWS account in your ECR.
  • Your workloads pull from your ECR, reducing repeated external downloads from cr.root.io. A safe approach for mutable tags (e.g., :prod, :latest) where the mirroring job detects tag changes and updates your cache accordingly.
  • Optional architecture pinning (e.g., mirror only linux/amd64) to avoid cross-platform surprises.

Prerequisites

You’ll need:
  • Your AWS account ID and AWS region(s) where you want the cache (ECR is regional).
  • Credentials for pulling from cr.root.io (username/password/token you already use).
  • A place to run a small scheduled job (anything that can run a script on a timer).
  • Docker installed (the mirroring flow uses docker pull/tag/push).

AWS authentication (required)

The mirroring job needs AWS credentials. Use either: Option A: AWS profile
Option B: Access keys
(And also set AWS_REGION in either case.)

Step 1 — Choose a destination naming convention in ECR

Pick a prefix in your ECR, for example: root-mirror/ Example mapping:
  • Source: cr.root.io/team/app:prod
  • Destination: <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/root-mirror/team/app:prod

Step 2 — Create the mirror repositories in ECR

You can either:
  • Pre-create the repositories you plan to mirror into (recommended for tighter IAM + predictability), or
  • Allow the mirroring job to auto-create repositories (simpler, requires additional permission).
For each source repo path (everything after cr.root.io/ up to :tag), create a matching ECR repo under your chosen prefix. Example:
  • Source: cr.root.io/team/app:prod
  • Create ECR repo: root-mirror/team/app
Create via AWS CLI:
Optional (recommended): add a lifecycle policy to limit storage growth (example keeps last 30 images):

Option B: Auto-create repositories from the script

If you prefer not to pre-create repos, the script can create them as needed. If you choose this, ensure the job role has:
  • ecr:CreateRepository
  • ecr:DescribeRepositories

Step 3 — Ensure your mirroring job can push to ECR

Your job needs permission to push images into ECR. Minimum AWS permissions commonly required:
  • ecr:GetAuthorizationToken
  • ecr:BatchCheckLayerAvailability
  • ecr:InitiateLayerUpload
  • ecr:UploadLayerPart
  • ecr:CompleteLayerUpload
  • ecr:PutImage
  • (optional) ecr:CreateRepository + ecr:DescribeRepositories (if auto-creating repos)
You can scope these permissions to root-mirror/* repositories for least privilege.

Step 4 — Define what to mirror (images.txt format)

Create an images.txt file.

Format

Each line is:
  • The first column is required: cr.root.io/...:tag
  • The second column is optional: architecture such as amd64 or arm64
    • If provided, the script treats it as linux/<arch> (it prepends linux/ internally)

Example images.txt

Why the optional arch column exists:
  • If you run the job from macOS (darwin/arm64) or a different architecture, you often still want to mirror linux/amd64.
  • Explicit arch avoids pulling the “wrong” platform variant (or failing to resolve).

Step 5 — Mirror images (with tag-change detection and retries)

What the script does (behavior)

The mirroring script is designed to be safe for mutable tags and reliable across environments:
  • Cross-platform logging: uses a portable UTC timestamp format (works on Linux + macOS).
  • Retries: wraps network-sensitive operations (especially docker push) with retries (configurable attempts + delay).
  • Architecture support: reads optional arch from images.txt and mirrors linux/<arch> when specified.
  • Change detection: avoids re-copying images unless the content changed:
    • It compares image config digests (SHA256 of the image config JSON), which is stable across OCI vs Docker v2 manifest format differences.
    • This prevents false-positive “changed” loops that can happen when only manifest formatting differs.

How tag-change detection works (in plain terms)

For each <image>:<tag> [arch]:
  1. Resolve the current config digest for the source tag in cr.root.io (optionally for a specific platform).
  2. Resolve the current config digest for the same tag in your ECR mirror repo (same platform).
  3. If digests match → skip (already cached).
  4. If digests differ or destination tag is missing → pull, tag, and push to ECR.

5.1 Create this mirroring script

Save as mirror_to_ecr.sh:

5.2 Running the script

Export required environment variables:
Then run:

Step 6 — Update workloads to pull from your ECR

Change pulls from:
  • cr.root.io/team/app:prod
to:
  • <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/root-mirror/team/app:prod
With mutable tags, your workloads will see updates after the mirror job runs and pushes the updated tag to your ECR. If you want the strongest “never change unexpectedly” guarantee, you can still deploy by digest, but it is not required for caching—your ECR tag will track the mirrored tag based on the script’s change detection.

Step 7 - Suggested scheduling (platform-agnostic)

The mirroring script is designed to be run repeatedly. Pick a cadence based on how often your mutable tags change (for example: every 15–60 minutes for frequently-changing tags, less often for stable version tags). You can schedule it in any environment that can run a command on a timer:
  • Cron on a VM/bastion/utility host
    • Install Docker + AWS CLI, store images.txt + mirror_to_ecr.sh, then add a cron entry.
    • Example (runs every 30 minutes):
  • CI scheduler
    • Most CI systems support scheduled workflows/pipelines.
    • Store images.txt in the repo, inject secrets (ROOT_USER/ROOT_PASS, AWS creds/profile) via the CI secret manager, run ./mirror_to_ecr.sh.
  • A scheduled container/job runner
    • Package the script + images.txt into a small container image (or mount them), then run it periodically using whatever scheduler you already use (Kubernetes CronJob, a container task scheduler, etc.).
    • Make sure Docker is available (either inside the job image, or via a Docker-enabled runner).
Tip: Send stdout/stderr to centralized logs (or at least a file) so you can alert on failures, and keep images.txt focused on the specific images/tags your production workloads actually use.

Networking note (optional)

If your workloads run in private subnets, consider configuring ECR VPC endpoints / PrivateLink so pulls can stay private and reduce NAT usage. The exact endpoint setup depends on your VPC architecture.

Troubleshooting

  • 401/403 pulling from cr.root.io: credentials/token expired or not authorized
  • Denied pushing to ECR: IAM role missing ecr:PutImage or upload permissions
  • Repository not found: either pre-create repos (Step 2 Option A) or set AUTO_CREATE_REPOS=true
  • Wrong architecture mirrored: add the second column (amd64 / arm64) in images.txt
  • Intermittent push failures: retries are built-in; if persistent, check network throttling / ECR limits and increase retry settings (attempts/delay)