Docker Images & Layers

beginner docker images layers registry

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.

Docker Image Layers
Writable Layer (container only)
Layer 4: CMD ["node", "app.js"]
Layer 3: COPY . /app
Layer 2: RUN npm install
Layer 1: FROM node:20-alpine (base image)
Each layer is read-only and identified by a SHA256 hash

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:alpine or node:20-slim. Tags are mutable — the owner can push a new image under the same tag. So node:20-alpine today might be different from node:20-alpine next 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.