Docker Image — The Blueprint for Every Container
What Is a Docker Image in Simple Terms?
A Docker image is a read-only package that contains everything needed to run an application — the code, the runtime, the libraries, the configuration files, and the operating system files. When you run an image, Docker creates a container from it. The image never changes — it is the template. The container is the running instance.
Think of an image like a cooking recipe. The recipe itself never changes — it tells you exactly what ingredients and steps to use. Every time you follow the recipe, you create a new meal (the container). Multiple chefs can follow the same recipe simultaneously to create multiple identical meals.
+------------------------------------------+| Docker Image (read-only template) || ubuntu:22.04 base || + Node.js 20 runtime || + npm packages from package.json || + application source code || + nginx config |+------------------------------------------+ | | docker run (creates) | v+------------------+ +------------------+ +------------------+| Container 1 | | Container 2 | | Container 3 || Running instance | | Running instance | | Running instance || Has own RW layer | | Has own RW layer | | Has own RW layer |+------------------+ +------------------+ +------------------+ All three share the same read-only image layers No duplication on diskHow Images Are Layered — The Union Filesystem
A Docker image is not a single monolithic file. It is a stack of layers. Each layer represents the filesystem changes made by one Dockerfile instruction. Understanding this is the key to understanding build speed, image size, and storage efficiency.
+------------------------------------------+| Layer 4: COPY . . | <- your source code| Size: 2MB |+------------------------------------------+| Layer 3: RUN npm install | <- installed node_modules| Size: 150MB |+------------------------------------------+| Layer 2: COPY package.json ./ | <- just the manifest| Size: 4KB |+------------------------------------------+| Layer 1: FROM node:20-alpine | <- Node.js + Alpine Linux| Size: 55MB |+------------------------------------------+ Total image size: ~207MBEach layer is identified by a SHA256 hash of its contentsLayers are immutable — once created, they never change. When Docker builds a new image, it only creates new layers for instructions that changed. Everything else is reused from the cache.
# See all layers of an imagedocker history node:20-alpine# IMAGE CREATED CREATED BY SIZE# a84f9c2b1d3e 3 weeks ago CMD ["node"] 0B# b72c8a9f4e1d 3 weeks ago ENTRYPOINT ["docker-entry 0B# c91d8b3f2a5e 3 weeks ago COPY docker-entrypoint.sh 550B# ... RUN apk add ... 40.2MB# ... ENV NODE_VERSION=20.10.0 0B# ... FROM alpine:3.18 7.34MB # See your own image layersdocker history my-payment-api:latest# This shows exactly how much each of your Dockerfile instructions added# The layer with the largest size is your optimization targetOverlayFS — How Layers Stack Into a Filesystem
Docker uses OverlayFS (Overlay Filesystem) to merge multiple read-only layers into a single unified view. When you run a container, Docker adds one writable layer on top:
+------------------------------------------+| Container Read-Write Layer | <- writes go here| (deleted when container is removed) | unique per container+==========================================+| Image Layer 4: App source code | <- read-only+==========================================+| Image Layer 3: npm node_modules | <- read-only+==========================================+| Image Layer 2: package.json | <- read-only+==========================================+| Image Layer 1: node:20-alpine base | <- read-only+==========================================+ | | OverlayFS merges these v+------------------------------------------+| Container sees one unified filesystem || /usr/bin/node exists (from Layer 1) || /app/package.json exists (from Layer 2) || /app/node_modules/ exists (from Layer 3) || /app/src/server.js exists (from Layer 4) |+------------------------------------------+When a container modifies a file that exists in a read-only layer, OverlayFS copies the file up to the writable layer and modifies it there — leaving the original layer untouched. This is called copy-on-write.
Image Naming and Tagging
# Full image reference format:# [registry/][owner/]name[:tag][@digest] # Docker Hub (default registry):nginx # Short form: nginx:latest on Docker Hubnginx:1.25 # Specific versionlibrary/nginx:1.25 # Full form on Docker Hub # Custom registry:registry.razorpay.in/payment-api:v3.1.0registry.razorpay.in/payment-api:v3.1.0-rc1registry.razorpay.in/payment-api:latest # Never use in production! # Digest reference (immutable — always the exact same image):nginx@sha256:a84f9c2b1d3e... # Tag an imagedocker tag my-image: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.0Working With Images
# List images on your local machinedocker images# REPOSITORY TAG SIZE# registry.razorpay.in/payment-api v3.1.0 207MB# nginx 1.25 187MB# node 20-alpine 55MB # List images with digestdocker images --digests# Shows SHA256 digest alongside each image # Inspect image metadatadocker image inspect nginx:1.25# Returns full JSON with: layers, exposed ports, environment, labels # See what layers make up the image and their sizesdocker history nginx:1.25 # Pull an image without running itdocker pull node:20-alpine # Remove an image from local storagedocker rmi nginx:1.24# Cannot remove if a container (even stopped) is using the image # Remove all dangling images (untagged, from old builds)docker image prune # Remove all unused images (not referenced by any container)docker image prune -a # Find dangling images (untagged = old build artifacts)docker images -f dangling=true# These accumulate on build machines and waste disk spaceImage ID vs Image Digest
# Image ID: a short hash of the image configuration# Can be different on different machines for the same image (rare but possible)docker images --format '{{.ID}} {{.Repository}}:{{.Tag}}'# a84f9c2b1d3e registry.razorpay.in/payment-api:v3.1.0 # Image Digest: SHA256 hash of the image manifest# Globally unique and immutable — the exact same image bytes anywheredocker images --digests | grep payment-api# registry.razorpay.in/payment-api v3.1.0 sha256:b72c8a9f4e1d... 207MB # Pull by digest for reproducible builds:docker pull nginx@sha256:a84f9c2b1d3e...# This ALWAYS gets exactly the same image# Even if someone pushed a new nginx:1.25 after your last pullDangling Images — Cleaning Up Build Artifacts
Every time you rebuild an image with the same tag, the old image loses its tag and becomes dangling — it still exists on disk but has no name.
# Dangling images look like this:docker images# REPOSITORY TAG IMAGE ID CREATED SIZE# <none> <none> a84f9c2b1d3e 2 hours ago 207MB <- dangling# <none> <none> b72c8a9f4e1d 1 day ago 198MB <- dangling# payment-api v3.1.0 c91d8b3f2a5e 3 minutes ago 207MB <- current # Remove all dangling imagesdocker image prune -f # On build machines, schedule this as a cron job# otherwise disk fills up with hundreds of old image layers# crontab entry: 0 3 * * * docker image prune -fTroubleshooting Reference
| Problem | Cause | Fix |
|---|---|---|
docker: Error response from daemon: pull access denied |
Not logged in to private registry | docker login registry.razorpay.in |
Error: No such image |
Image not pulled locally | docker pull image:tag |
Cannot remove image: image is being used by stopped container |
Stopped container references image | docker rm container-id then docker rmi image |
| Disk filling up on build machine | Dangling images accumulating | docker image prune -a -f |
| Different machines get different images for same tag | Tag was overwritten | Pin by digest: image@sha256:... |
PLACEMENT PRO TIP**Tip:** Run `docker history your-image:tag --no-trunc` to see the full Dockerfile instruction that created each layer. This is the fastest way to understand what is taking up space in a large image — each layer shows its size and the exact command that created it.
REMEMBER THIS**Remember:** Image layers are shared between images on the same host. If you have 10 containers all running from `node:20-alpine`, the Alpine base layers are stored once on disk — not 10 times. This storage efficiency is a major reason container images are so much more practical than VMs for running many services on the same host.
COMMON MISTAKE / WARNING**Common Mistake:** Using the `:latest` tag in production. The `latest` tag is just a convention — it means whatever was last pushed with that tag. If someone pushes a broken build and tags it `latest`, every deployment that pulls `latest` will get the broken build. Always tag images with an immutable identifier like a git SHA or a semantic version.
COMMON MISTAKE / WARNING**Security:** Base images inherit CVEs (known vulnerabilities) from the operating system packages they include. An `ubuntu:22.04` base typically has 200+ known CVEs. An `alpine:3.18` base has 0-5. Always scan your images with Trivy (`trivy image your-image:tag`) before pushing to production and fail CI if CRITICAL severity CVEs are found.