Amazon ECR Public

Auth

ECR Public (public.ecr.aws) is a separate registry from ECR private with a different auth flow. ocync auto-detects it via the canonical hostname.

Authenticated path:

  • ocync calls ecr-public:GetAuthorizationToken (always against us-east-1; ECR Public is single-region).
  • The returned token is base64-decoded to extract the password half (AWS:<password>).
  • That password is used as HTTP Basic credentials in the standard OCI /v2/token Bearer exchange.

Anonymous pulls work without AWS credentials but have lower per-IP rate limits: AWS documents unauthenticated pulls at 1 per second and not adjustable, against 10 per second authenticated. Use authenticated access for any non-trivial sync workload.

Authenticating does not buy an exemption on tag listing, though. See below.

Notable behaviors:

  • No auth_type value exists for ECR Public; it is reachable only via auto-detection on public.ecr.aws. Setting auth_type: ecr_public is a parse error.
  • No BatchCheckLayerAvailability. ECR Public uses per-blob HEADs via the OCI Distribution path, not the ECR batch API.
  • Lower rate limits than ECR private: pulls as above, and for writes AWS documents InitiateLayerUpload and CompleteLayerUpload at 10 per second (100 on ECR private), UploadLayerPart at 260 (500), and PutImage at 10, the same as ECR private (quotas). Read paths share a single window.
  • Tag listing has throttled below the documented pull rate. Measured 2026-08-28: a 95-mapping --dry-run against public.ecr.aws with AWS credentials loaded drew repeated 429s on TagList, halving the AIMD window from 11 to 6 to 3, while ocync’s token bucket for ECR Public reads was pacing at 8 requests per second. AWS publishes no TPS quota for tag listing at all; the 8 is derived from the 10 TPS authenticated pull quota, and the read window groups tag listing in with pulls. Treat the read pacing for this registry as measured rather than documented. Measured again 2026-10-30, with credentials loaded: a 45-mapping --dry-run listed every tag in 65 seconds with no 429 on tag listing, retried or not. Throttling depends on load, so the pacing stays as measured in August.
  • Because of the above, list_tags retries a 429 rather than surfacing it. Before that, each throttle failed a whole mapping: the same run lost 5 mappings and exited 1. With the retry it exits 0 and loses none.
  • Anonymous manifest reads throttle near the documented rate. Measured 2026-10-30: reading 235 manifests by digest one after another, anonymously, took about 90 seconds and drew about 30 429s, each recovered on retry. Blob GETs, which redirect to CloudFront, drew no 429s when the benchmark pre-warm fetched them at one request per 50 ms on 2026-10-30. The current pre-warm fetches them 8 at a time, and in two full runs it fetched 973 blobs each with 0 failed and 0 past the deadline. A few small blobs are answered directly with a 200 and no cache header instead of a redirect to CloudFront, so they are neither hits nor misses.
  • Blob downloads come from a CDN that speaks HTTP/2, and it holds a stalled request for minutes. Measured 2026-10-27: when blob bodies opened ahead of their uploads filled the connection’s receive window, a cold sync of the alpine 3.20 index (39 blobs, 28,653,742 bytes) to ECR sat with nothing moving for about 330 seconds until the CDN closed the requests, in 2 of 9 runs. At the stall the CDN connection carried 38 open blob GETs and had delivered 74,613 bytes, consistent with one 65,535 byte window plus their response headers. ocync now downloads blobs over HTTP/1.1, so an unread body holds back only its own connection.

CLI example

# Anonymous pull (works without AWS credentials but low rate limit).
ocync copy \
  public.ecr.aws/docker/library/alpine:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/alpine:latest

For authenticated pulls, ensure ambient AWS credentials are present (env vars, shared credentials, IRSA, etc.); ocync will pick them up automatically.

Kubernetes deployment

Anonymous pulls require no AWS identity, so the simplest pod has no workloadIdentity block at all — accept the lower per-IP rate limit. For authenticated reads (higher rate limit), ocync uses whatever AWS identity the workload has, exactly as for ECR private. See ECR Kubernetes deployment for the EKS Pod Identity and IRSA setups; both apply unchanged.