A Docker image is a read-only template that contains everything needed to run an application — the code, runtime, libraries, environment variables, and config files. When we run an image, it becomes a container (a running instance of that image).
Think of it like a class vs an object. The image is the class. The container is the object we create from it.
Image layers
Every Docker image is made up of stacked layers. Each instruction in a Dockerfile (like RUN, COPY, ADD) creates a new layer. These layers are read-only and stacked on top of each other.
When we run a container, Docker adds a thin writable layer on top. Any changes we make inside the container (editing files, writing logs) happen in this writable layer. The original image layers stay untouched.
Layer caching and sharing
This is where layers get really clever. Docker caches each layer. If we rebuild an image and a layer hasn’t changed, Docker reuses the cached version instead of rebuilding it. This makes builds fast.
Even better, different images can share layers. If we have 5 Node.js apps all using node:20-alpine as the base, that base layer is stored only once on disk. All 5 images point to the same layer.
# See the layers of an image
docker history nginx:alpine
# See image size (shared layers don't count twice)
docker system df
# Inspect detailed layer info
docker inspect nginx:alpine
Pulling from registries
A registry is a storage service for Docker images. When we do docker pull nginx, Docker downloads the image from a registry.
Common registries:
- Docker Hub (
docker.io) — the default, largest public registry - GitHub Container Registry (
ghcr.io) — tied to our GitHub repos - AWS ECR, Google GCR, Azure ACR — cloud-specific registries
# Pull from Docker Hub (default)
docker pull nginx:alpine
# Pull from GitHub Container Registry
docker pull ghcr.io/pman47/gyaan:latest
# Push our own image to a registry
docker tag my-app:latest ghcr.io/username/my-app:latest
docker push ghcr.io/username/my-app:latest
Tags vs Digests
We reference images in two ways:
- Tags are human-readable labels like
nginx:alpineornode:20-slim. Tags are mutable — the owner can push a new image under the same tag. Sonode:20-alpinetoday might be different fromnode:20-alpinenext month. - Digests are the SHA256 hash of the image manifest. They’re immutable and always point to the exact same image.
# Pull by tag (mutable — image can change)
docker pull nginx:1.27
# Pull by digest (immutable — always the exact same image)
docker pull nginx@sha256:abc123def456...
# See the digest of a pulled image
docker images --digests
For production deployments, using digests (or at least specific version tags like node:20.11.1-alpine) is safer than using generic tags like latest or node:20. We don’t want a surprise update breaking our app.