Container Images in AWS Explained: ECR, Lambda, ECS, EKS
A container image is a portable, self-contained package of your application: code, runtime, libraries, and OS dependencies all in one bundle. You build it once, push it to Amazon ECR, and run it on Lambda, ECS, EKS, or App Runner. Every environment runs the exact same image.
Simple explanation
Before containers, the classic problem was “it works on my machine.” The version of Python on your laptop, the library versions, the OS packages — none of it matched production. Containers solve this by packaging everything together.
Three distinct things to keep straight:
- Container image: the static bundle. Built once, never changes. Like a template or blueprint.
- Container: a running instance of that image. Isolated, ephemeral. Stop it and it’s gone.
- Registry: a storage service for images. Amazon ECR is AWS’s registry. Docker Hub is the public alternative.
You build an image locally, push it to a registry, and a runtime (Lambda, ECS, EKS) pulls and runs it. The image itself is never modified. If you need a change, you build a new image.
ECR is storage, not execution. Lambda, ECS, EKS, and App Runner are the runtimes that pull your image and actually run it. These are separate responsibilities.
Before standardised shipping containers, every port and ship handled cargo differently. A standardised container changed that: any ship, truck, or crane works with the same box, no special handling required. Container images work the same way. Any AWS compute service pulls and runs your image without platform-specific configuration.
What a container image actually contains
A container image packages:
- Base OS layer: typically a minimal Linux distribution like Alpine, Debian slim, or Amazon Linux
- Runtime: Python 3.12, Node.js 20, Java 21, or whichever language your app uses
- System libraries: OS-level packages your runtime or app depends on
- Application dependencies: your
pip installornpm installoutput - Application code: your source files
- Configuration: environment defaults, exposed ports, startup command
The image is immutable. Once built, it does not change. When a container starts from that image, it gets a writable layer on top, but anything written there is discarded when the container stops. Persistent state belongs in a database or S3, not inside the container.
How container images work in AWS
The full workflow from code to running container:
- Write a Dockerfile: instructions for building the image.
- Build the image locally:
docker buildexecutes those instructions layer by layer. - Test it locally:
docker runstarts a container on your machine before anything goes to AWS. - Push to Amazon ECR: authenticate with
aws ecr get-login-password, thendocker push. - Deploy to your runtime: point Lambda, ECS, EKS, or App Runner at the ECR image URI.
- Update by pushing a new image: tag a new version, push, and update your service to use it.
Dockerfile → docker build → docker run (local test) → ECR → Lambda / ECS / EKS / App RunnerAlways test locally with docker run before pushing to ECR. The runtime pulls from ECR, not your laptop, so catching issues locally costs nothing. Pushing a broken image to production costs a lot.
Dockerfile example
A typical Python web application Dockerfile:
# Start from an official Python base image
FROM python:3.12-slim
# Set the working directory inside the container
WORKDIR /app
# Copy requirements first (enables layer caching)
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application code
COPY . .
# Non-root user for security
RUN useradd -m appuser && chown -R appuser /app
USER appuser
# Expose port
EXPOSE 8000
# Start the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Build it:
docker build -t my-app:1.0 .Test it locally before pushing:
docker run -p 8000:8000 my-app:1.0
# Application available at http://localhost:8000The name:tag format identifies images. my-app:1.0 means the image named my-app at version 1.0. Tags are typically version numbers or Git commit SHAs, not latest (more on that in the mistakes section).
Image layers explained
Each Dockerfile instruction that modifies the filesystem creates a new layer. Layers stack to form the final image. They are immutable and cached.
Why instruction order matters:
- Docker rebuilds from the first changed instruction downward.
- Put slow, infrequent changes first: base image, system packages, dependency installs.
- Put fast, frequent changes last: copying your application code.
- This way, a code change only rebuilds the final layer, not the full dependency install.
Always copy your dependency manifest (requirements.txt, package.json) and run your install command before copying the rest of your application code. This single ordering change can cut rebuild times from minutes to seconds.
When pushing to ECR, Docker transfers only layers not already present in the registry. A code-only change might push a few MB even if the full image is 500 MB.
AWS services that run container images
| Service | What it does | Best fit | Main trade-off |
|---|---|---|---|
| AWS Lambda | Runs containers as serverless functions (up to 10 GB images) | Event-driven, short-lived tasks; large dependency sets | 15-minute max duration; cold starts |
| Amazon ECS (Fargate) | Serverless container orchestration with no EC2 to manage | Long-running services, microservices, batch jobs | Slightly higher cost than self-managed EC2 |
| Amazon ECS (EC2) | Container orchestration on your own EC2 instances | GPU workloads, specific instance types, cost optimisation | You manage EC2 patching and capacity |
| Amazon EKS | Managed Kubernetes cluster for container workloads | Teams that need Kubernetes APIs, multi-cloud portability | Steeper learning curve; higher operational overhead |
| AWS App Runner | Fully managed: give it an image URI and it handles everything | Simple web apps and APIs; minimal ops overhead | Less control; not suited for complex workloads |
The core decision:
- Use Lambda for short-lived, event-driven workloads: API calls, file processing, scheduled jobs.
- Use ECS for running containers without Kubernetes complexity. Lambda vs ECS covers the overlap in detail.
- Use EKS when your team needs the Kubernetes ecosystem. See EKS vs ECS for the full comparison.
- Use App Runner for the simplest path to a running web app or API. Deploy directly from an ECR image with no infrastructure to configure.
For a guided decision, see Choosing Between EC2, Lambda, and Containers in AWS.
When to use container images
Container images are the right choice when:
- Large dependencies: ML libraries, data science stacks, or ORMs that exceed Lambda’s 250 MB zip limit.
- Consistent runtime across environments: you want development, staging, and production to run the exact same image.
- Microservices: each service is independently deployable as its own image.
- Batch jobs: container-based jobs run reproducibly on AWS Batch or ECS.
- Custom runtimes: languages or versions not natively supported by Lambda’s managed runtimes.
- Web apps and APIs: especially when you need long-running processes, WebSockets, or connection pooling.
When a simpler option is better:
- A small Lambda function with standard library dependencies fits comfortably in a zip deployment. No Docker toolchain needed.
- A simple static site or single-file script does not need containerisation.
- If you have no existing container tooling and your workload is small, start with Lambda zip and only move to containers when you hit a real constraint.
Key comparisons
Container image vs container
A container image is static: a file on disk or in a registry. A container is dynamic: a running process created from that image. You can run many containers from the same image simultaneously. Stopping a container leaves the image unchanged. See Containers vs Virtual Machines in AWS for how containers compare to EC2 instances.
Lambda zip deployment vs Lambda container image
Lambda supports two deployment formats: zip files (up to 250 MB) and container images (up to 10 GB). Use zip for small functions with minimal dependencies. It deploys faster and has no Docker toolchain requirement. Use container images when you exceed the 250 MB limit, need a custom runtime, or want consistent builds across Lambda and ECS.
Containers vs virtual machines in AWS
EC2 instances are full virtual machines with a separate OS and kernel. They are heavier but more isolated. Containers share the host kernel, start in seconds, and pack more tightly onto hardware. Containers are not always the right answer. Databases, legacy applications with kernel dependencies, and workloads requiring full OS control often belong on EC2. The full breakdown is in Containers vs Virtual Machines in AWS.
Amazon ECR in context
Amazon ECR (Elastic Container Registry) is AWS’s managed container registry. It stores your images privately, integrates with IAM for access control, and delivers images to Lambda, ECS, EKS, and App Runner from the same region. No cross-region egress costs, no Docker Hub rate limits.
Pushing an image to ECR:
# 1. Create a repository
aws ecr create-repository \
--repository-name my-app \
--region us-east-1
# Output includes the repository URI:
# 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app
# 2. Authenticate Docker to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
# 3. Tag the local image with the ECR URI
docker tag my-app:1.0 \
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0
# 4. Push to ECR
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0Good ECR practices:
- Enable immutable tags: prevents overwriting an existing tag, protecting production from silent changes.
- Enable image scanning: ECR scans for known CVEs on push. Fix high-severity findings before deploying.
- Set lifecycle policies: automatically expire old untagged images to control storage costs.
- Organise by service: one repository per application, not a single catch-all repository.
For automation, see Building Docker Images with AWS CodeBuild and CI/CD Pipelines for Amazon ECS.
Image tags vs digests
Every ECR image URI follows this format:
account-id.dkr.ecr.region.amazonaws.com/repository-name:tagFor example:
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0Tags are mutable. Anyone with push access can change what my-app:latest points to. Digests are immutable. A digest is a SHA256 hash of the image content:
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app@sha256:abc123...Never deploy from :latest in production. The tag is mutable and can silently change to a different image. Use a specific version tag with ECR immutable tag settings enabled, or reference by digest. That way you always know exactly what is running.
Common mistakes
Never embed secrets in a Dockerfile. Credentials added via ENV, RUN commands, or copied files are stored in image layers. Anyone who can pull the image can read them. Pass secrets at runtime via environment variables, AWS Secrets Manager, or IAM roles.
- Putting secrets in the image. See the warning above. This is the most critical mistake and the hardest to recover from once an image is in a shared registry.
- Using
:latestin production.latestis mutable and can silently change. Pin to a specific version tag or image digest so you always know exactly what is running. - Oversized images. Build tools, test frameworks, and documentation bloat images and slow deployments. Use multi-stage builds: compile in one stage, copy only the final artifact to a minimal runtime image.
- Poor layer caching. Copying all application code before installing dependencies means a one-line code change invalidates the entire dependency cache. Always copy dependency manifests first, install, then copy code.
- Running as root. If a container running as root is compromised, the attacker has root. Add a non-root user:
RUN useradd -m appuser && USER appuser. - Not scanning images. Enable ECR image scanning. Unpatched CVEs in base images are a real attack surface. Scan on push and periodically rescan images already in production.
- Confusing image storage with execution. ECR stores images. It does not run them. Lambda, ECS, EKS, and App Runner are the runtimes. A healthy image sitting in ECR does nothing until a runtime pulls and starts it.
Summary
- Container images bundle your application code, runtime, and dependencies into a portable, immutable package.
- The workflow: write a Dockerfile, build locally, test locally, push to ECR, deploy to Lambda, ECS, EKS, or App Runner.
- Layers are cached. Put stable instructions first and application code last for fast rebuilds.
- ECR stores images. Lambda, ECS, EKS, and App Runner run them. These are separate responsibilities.
- Use image digests or immutable version tags in production. Never deploy from
:latest. - Lambda container images overcome the 250 MB zip limit. Use them for large dependency sets.
- Enable ECR image scanning and lifecycle policies to keep repositories secure and clean.
Frequently asked questions
What is a container image?
A container image is a portable, immutable bundle containing your application code, runtime, OS libraries, and dependencies. It is built once and never modified. When a runtime like Lambda, ECS, or EKS pulls it and starts it, a container is created from that image.
What is the difference between a container image and a container?
A container image is static: a package on disk or in a registry. A container is dynamic: a running process created from that image. Many containers can run from the same image simultaneously. Stop a container and the image is unchanged.
Which AWS services can run container images?
AWS Lambda (up to 10 GB images), Amazon ECS on Fargate or EC2, Amazon EKS, and AWS App Runner all run container images pulled from Amazon ECR or other OCI-compatible registries.
When should I use Lambda container images instead of zip files?
Use a container image when your function and dependencies exceed 250 MB (Lambda's zip limit), when you need a custom runtime, or when you want a single build process that works across Lambda and ECS. Use zip deployment for small, simple functions where the Docker toolchain adds unnecessary complexity.
Do I need Amazon ECR to use containers in AWS?
No. Lambda, ECS, EKS, and App Runner support Docker Hub and other OCI-compatible registries. However, ECR is the recommended choice: images pull from the same region with no egress cost, IAM controls access, and image scanning integrates automatically.