Container Image
A read-only, layered filesystem snapshot containing an application and everything it needs to run — code, runtime, libraries, and configuration. Container images are the templates from which containers are created.
Container Image — The Template for Every Container
What Is a Container Image in Simple Terms?
A container image is a packaged, portable snapshot of a complete application environment. It contains your application code, the runtime it needs (Node.js, Python, Go), all the library dependencies, configuration files, and even the operating system files needed to run. When you run an image, Docker creates a container from it.
The relationship between an image and a container is exactly like the relationship between a class and an object in programming — the image is the class definition (static, never changes), the container is the running instance (active, has state).
+------------------------------------------+| Container Image (read-only template) || || OS files (Alpine Linux) || + Node.js 20 runtime || + npm packages (node_modules) || + application source code || + environment defaults |+------------------------------------------+ | | docker run (creates) |+--------v---------+ +------------------+| Container 1 | | Container 2 || Running instance | | Running instance || Has own RW layer | | Has own RW layer || Files it writes | | Files it writes || are unique to it | | are unique to it |+------------------+ +------------------+ Both share the same image layers No disk duplicationHow Images Are Built
Container images are built from a Dockerfile — a text file with instructions that Docker executes one by one, each creating a new layer:
# Each instruction creates one layer in the imageFROM node:20-alpine # Layer 1: base OS + Node.js (55MB)WORKDIR /app # Layer 2: set working directory (0B)COPY package.json ./ # Layer 3: dependency manifest (4KB)RUN npm ci --omit=dev # Layer 4: installed packages (150MB)COPY dist/ ./dist/ # Layer 5: compiled source code (2MB)CMD ["node", "dist/server"] # Layer 6: default startup command (0B) # Final image: ~207MB, 6 layers# Build an image from a Dockerfiledocker build -t payment-api:v3.1.0 . # See all layers and their sizesdocker history payment-api:v3.1.0# IMAGE CREATED BY SIZE# a84f9c2b1d3e CMD ["node" "dist/server"] 0B# b72c8a9f4e1d COPY dist/ ./dist/ 2MB# c91d8b3f2a5e RUN npm ci --omit=dev 150MB <- biggest layer# ... FROM node:20-alpine 55MBImage Layers and Storage Efficiency
Three containers running from the same image: +----------+ +----------+ +----------+| api-1 RW | | api-2 RW | | api-3 RW || ~1MB | | ~1MB | | ~1MB |+----------+ +----------+ +----------+ | | | +----+----+----+----+ | +---------v----------+ | Shared image layers| | ~207MB (one copy) | +--------------------+ Total disk used: 210MB (not 207 x 3 = 621MB)Each container only adds its unique read-write layerImage Naming and Tagging
# Image reference format: [registry/][owner/]name[:tag][@digest] # Docker Hub (default registry)nginx # = docker.io/library/nginx:latestnginx:1.25 # specific version # Private registryregistry.razorpay.in/payment-api:v3.1.0905418385260.dkr.ecr.ap-south-1.amazonaws.com/payment-api:v3.1.0 # Tag an image with a new namedocker tag payment-api:latest \ registry.razorpay.in/payment-api:v3.1.0 # Push to registrydocker push registry.razorpay.in/payment-api:v3.1.0 # Pull from registrydocker pull registry.razorpay.in/payment-api:v3.1.0 # List local imagesdocker images# REPOSITORY TAG IMAGE ID SIZE# payment-api v3.1.0 a84f9c2b1d3e 207MB# nginx 1.25 b72c8a9f4e1d 187MB # Remove an imagedocker rmi payment-api:v3.1.0 # Remove all unused imagesdocker image prune -aInspecting Images
# Full image metadatadocker image inspect nginx:1.25# Returns JSON with: layers, exposed ports, environment, entrypoint, labels # See image layers and sizesdocker history nginx:1.25 --no-trunc # Check image sizedocker images nginx:1.25# REPOSITORY TAG SIZE# nginx 1.25 187MB # Scan for vulnerabilitiestrivy image nginx:1.25# Finds CVEs in OS packages and application dependencies # Analyse layer efficiencydive nginx:1.25# Interactive TUI showing each layer contentImage vs Container — Key Differences
| Image | Container | |
|---|---|---|
| State | Static, read-only | Running, has read-write layer |
| Storage | On disk as layers | In memory + thin RW layer |
| Lifetime | Permanent until deleted | Created and destroyed |
| Count | One image | Many containers from one image |
| Modification | Requires rebuilding | Writes go to container RW layer |
Troubleshooting Reference
| Problem | Cause | Fix |
|---|---|---|
pull access denied |
Not logged in to private registry | docker login registry-hostname |
image not found |
Wrong tag or registry | Check exact image name and tag |
| Large image size | Dev dependencies, wrong base image | Multi-stage build, use alpine base |
| Image has CRITICAL CVEs | Outdated base image or packages | Update base image, run trivy image |
| Disk full on build machine | Old images accumulating | docker image prune -a -f |
TipUse
docker history image:tagto understand what is inside an image before running it. Each line shows one Dockerfile instruction and how much space it added. The largest layers are your optimisation targets — usuallyRUN npm installorRUN apt-get installinstructions.
RememberImage layers are immutable and shared. If you have 10 containers all running from
nginx:1.25, the 187MB image is stored once on disk — not 10 times. Each container only adds a tiny read-write layer for its own changes. This efficiency is a core reason containers are so much faster and cheaper than VMs.
Common MistakeUsing
:latestas the image tag in production. Thelatesttag is mutable — it points to whatever was last pushed with that tag. If a broken build is taggedlatest, every deployment gets the broken version. Always use an immutable tag like a git commit SHA or semantic version:payment-api:a84f9c2borpayment-api:v3.1.0.
Frequently Asked Questions
Why are container images built in layers instead of as a single flat filesystem?
Each instruction in a Dockerfile (RUN, COPY, ADD) creates a new layer, and layers are content-addressed and cached — so if only your application code changes but base OS and dependency layers stay the same, rebuilding and pulling only transfers the changed layer, not the whole image. This is also why Dockerfile instruction order matters for build speed: put things that change rarely (base image, dependency installs) before things that change often (application code copy).
What's a common mistake that bloats container images?
Not cleaning up build-time artifacts (compiler toolchains, package manager caches, temp files) within the same layer they were created, since a later `rm` in a separate RUN instruction doesn't shrink earlier layers — the deleted files' bytes are still in the image history. Multi-stage builds solve this properly: build in one stage with the full toolchain, then copy only the compiled output into a minimal final-stage image, keeping production images small and reducing attack surface.