What Is Amazon ECR and How to Push a Docker Image to It
You've built a Docker image locally and now need to get it into AWS so ECS, EKS, or Lambda can pull it. Amazon ECR is the natural landing zone — but the first push always trips people up on the authentication step, which doesn't behave like a standard docker login.
TL;DR: Amazon ECR Image Push at a Glance
| Step | Command / Action | Why It Matters |
|---|---|---|
| 1. Create repository | aws ecr create-repository | Defines the namespace and lifecycle scope |
| 2. Authenticate Docker | aws ecr get-login-password | docker login | ECR uses short-lived tokens, not static passwords |
| 3. Tag image | docker tag <local> <ecr-uri> | Maps local image to the ECR registry URI |
| 4. Push image | docker push <ecr-uri> | Uploads layers to the repository |
How Amazon ECR Works
Amazon Elastic Container Registry (ECR) is a fully managed OCI-compatible container registry. Each AWS account gets a private registry per region, addressed as <account-id>.dkr.ecr.<region>.amazonaws.com. Within that registry you create named repositories — one per application or image family is the common pattern.
Authentication is the part that catches engineers off guard. ECR doesn't issue long-lived credentials. Instead, you call the ECR API to get a temporary authorization token (valid for 12 hours), then pipe that token directly into docker login. The Docker daemon caches it in your credential store until it expires. If a CI pipeline starts failing with no basic auth credentials after running fine for months, an expired token is almost always the cause.
Image layers are stored in S3 under the hood, but that's fully abstracted — you interact with ECR exclusively through the Docker CLI and AWS CLI. Pushes are deduplicated at the layer level, so only changed layers transit the network.
my-app:latest"] --> B["aws ecr get-login-password"] B --> C["ECR API
Returns Auth Token"] C --> D["docker login
Token cached 12h"] A --> E["docker tag
Add ECR URI"] D --> F["docker push"] E --> F F --> G["ECR Registry
Layer storage"] G --> H["ECS / EKS
Pull at runtime"]
- Local build — your
docker buildproduces a tagged image in the local daemon. - Token exchange —
aws ecr get-login-passwordcalls the ECR API and returns a base64-encoded password. This is piped intodocker login, which stores it in the local credential store. - Tag —
docker tagadds an ECR-addressed tag to the existing image without copying layers. - Push — Docker pushes only layers the registry doesn't already have, then writes the manifest.
- Pull (runtime) — ECS task definitions or Kubernetes pods reference the ECR URI. The compute plane authenticates using the task/node IAM role and pulls the image.
Prerequisites
- AWS CLI v2 installed and configured (
aws configureor environment credentials set) - Docker Engine running locally
- IAM permissions:
ecr:CreateRepository,ecr:GetAuthorizationToken,ecr:BatchCheckLayerAvailability,ecr:InitiateLayerUpload,ecr:UploadLayerPart,ecr:CompleteLayerUpload,ecr:PutImage
The minimal IAM policy for a developer or CI role pushing to a specific repository looks like this:
🔽 IAM policy — ECR push permissions (click to expand)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuthToken",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:us-east-1:123456789012:repository/my-app"
}
]
}
ecr:GetAuthorizationToken is a control-plane call that doesn't scope to a specific repository — it always requires "Resource": "*". Trying to restrict it to an ARN silently denies the token request, which surfaces as a confusing auth failure rather than an explicit permission error.
Step 1: Create the ECR Repository
A repository must exist before you can push. Creating one is a single CLI call. The repository name becomes part of the image URI, so use something that maps clearly to your service.
aws ecr create-repository \
--repository-name my-app \
--region us-east-1
The response includes the repositoryUri — copy it. You'll use it in the tag and push steps. It follows the pattern 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app.
If the repository already exists, this command returns a RepositoryAlreadyExistsException. To retrieve the URI of an existing repository:
aws ecr describe-repositories \
--repository-names my-app \
--region us-east-1 \
--query 'repositories[0].repositoryUri' \
--output text
Step 2: Authenticate Docker to Amazon ECR
This is the step most engineers get wrong the first time. ECR doesn't accept your AWS credentials directly — Docker needs a registry-specific password, which you fetch from the ECR API and pipe straight into docker login.
aws ecr get-login-password \
--region us-east-1 \
| docker login \
--username AWS \
--password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
The username is always the literal string AWS — not your IAM username, not your account ID. The password is the token returned by get-login-password. A successful login prints Login Succeeded and stores the token in Docker's credential store.
Think of this like a parking garage token: the AWS API issues a time-limited pass, Docker presents it at the gate, and it works until it expires — regardless of what your IAM credentials are doing in the background.
The token is valid for 12 hours. In CI pipelines, re-authenticate at the start of each job rather than assuming a cached token is still valid.
Step 3: Tag Your Local Image for ECR
Docker identifies push destinations by the image tag's registry hostname. Your locally built image has a short name like my-app:latest — Docker has no idea that maps to ECR. You add a tag that includes the full ECR URI.
docker tag my-app:latest \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
This doesn't copy the image — it just adds an alias. Both tags point to the same image layers in the local daemon. Use a specific version tag alongside latest in production; relying solely on latest makes rollbacks harder because the tag is mutable.
docker tag my-app:1.4.2 \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.4.2
Step 4: Push the Image to Amazon ECR
With authentication cached and the tag pointing at ECR, the push is a standard Docker command.
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
Docker uploads only the layers the registry doesn't already have. If you're pushing a new tag of an image that shares most layers with a previous push, only the changed layers transfer — the rest are confirmed present via BatchCheckLayerAvailability and skipped.
Verify the push landed correctly:
aws ecr describe-images \
--repository-name my-app \
--region us-east-1 \
--query 'imageDetails[*].{Tag:imageTags,Pushed:imagePushedAt,Size:imageSizeInBytes}' \
--output table
Complete Push Sequence — All Four Steps Together
- Build produces a local image tagged
my-app:latest. - Token fetch authenticates the local Docker daemon to the ECR registry endpoint for 12 hours.
- Re-tag maps the local name to the full ECR URI.
- Push transmits only new layers; the manifest is written last.
- ECS or EKS pulls the image using the task/node IAM role — no separate auth step needed at runtime.
Common Failure: 'no basic auth credentials' After a Working Push
The symptom: push worked yesterday, fails today with no basic auth credentials or authorization token has expired. The misdiagnosis is usually IAM — engineers start checking policies, rotating keys, checking SCPs. None of that is the problem.
The actual cause: the 12-hour ECR token expired. Docker's credential store still has an entry for the ECR registry hostname, but the stored password is no longer valid. Re-running get-login-password | docker login fixes it immediately.
In CI, the fix is structural: always run the authentication step as the first action in the job, not once during pipeline setup. Pipelines that cache Docker credentials across jobs will hit this reliably once the token age crosses 12 hours.
Enabling Image Tag Immutability (Recommended)
By default, ECR allows pushing a new image to an existing tag — :latest can be overwritten silently. In production environments, tag immutability prevents accidental overwrites and makes deployments auditable.
aws ecr put-image-tag-mutability \
--repository-name my-app \
--image-tag-mutability IMMUTABLE \
--region us-east-1
With immutability enabled, pushing to an existing tag returns an ImageTagAlreadyExistsException. This forces explicit versioning — which is the behavior you want in any environment where rollback matters.
Setting a Lifecycle Policy to Control Storage Costs
ECR charges for stored image data. Without a lifecycle policy, every pushed image accumulates indefinitely. A common pattern is to retain the last N tagged images and expire untagged layers automatically.
🔽 Lifecycle policy — keep last 10 tagged images (click to expand)
aws ecr put-lifecycle-policy \
--repository-name my-app \
--region us-east-1 \
--lifecycle-policy-text '{
"rules": [
{
"rulePriority": 1,
"description": "Expire untagged images after 1 day",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Keep last 10 tagged images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
}
]
}'
Pricing and storage limits vary — always check the official ECR pricing page for current rates.
Pulling the Image at Runtime (ECS Example)
Once the image is in ECR, ECS task definitions reference it by the full URI. The ECS container agent authenticates using the task execution role — no manual docker login required on the host.
{
"containerDefinitions": [
{
"name": "my-app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.4.2",
"essential": true
}
],
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
}
The task execution role must include the AmazonECSTaskExecutionRolePolicy managed policy, which grants ecr:GetAuthorizationToken, ecr:BatchGetImage, and ecr:GetDownloadUrlForLayer. Without it, the task fails at image pull with an access denied error before any application code runs.
Wrap-Up: Pushing Docker Images to Amazon ECR
The full Amazon ECR push workflow comes down to four operations: create the repository, authenticate with a short-lived token, re-tag the local image with the ECR URI, and push. The authentication model is the only genuinely non-obvious part — once you understand that ECR tokens are time-limited and must be refreshed, the rest behaves like any OCI-compatible registry.
For next steps, consider enabling ECR image scanning to catch known CVEs on push, and review the ECR with ECS documentation for task execution role configuration details.
Glossary
| Term | Definition |
|---|---|
| ECR Repository | A named namespace within your account's private ECR registry that stores all versions of a single image. |
| Authorization Token | A short-lived (12-hour) credential issued by the ECR API, used to authenticate the Docker daemon to the registry endpoint. |
| Image URI | The full address of an image in ECR: <account-id>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>. |
| Tag Immutability | A repository setting that prevents an existing image tag from being overwritten by a subsequent push. |
| Task Execution Role | An IAM role assumed by the ECS container agent to pull images from ECR and retrieve secrets — distinct from the task's own IAM role. |
Comments
Post a Comment