Skip to content
DevOps AI ToolKit
Newsletter
All guides
Docker with AI By James Joyner IV · · 8 min read

What Is a Docker Image? A Practical Mental Model for Developers

What is a Docker image? A clear, practical guide to image layers, the union filesystem, image vs. container, tags vs. digests, registries, and hygiene.

  • #docker
  • #containers
  • #images

If you use Docker every day but couldn’t explain what an image actually is, you’re in good company — most people learn the commands long before they learn the model. The short version: a Docker image is a layered, read-only template that describes a filesystem and how to start a process. Once that clicks, a lot of confusing behavior — caching, image bloat, the latest tag biting you — stops being mysterious and starts being predictable.

In this post I want to give you the mental model I actually use, then show you how to verify each piece with commands you can run right now.

The mental model: a stack of read-only layers

Here’s the one sentence I’d attach to your brain: an image is a stack of read-only layers; a container is that stack plus a thin writable layer on top.

Each layer is a set of filesystem changes — files added, modified, or deleted relative to the layer beneath it. Docker stacks these layers and presents them as a single unified filesystem using a union filesystem (on most modern setups, overlay2). Your process inside the container sees one coherent /, but under the hood it’s several read-only layers merged together.

That “read-only” part matters. The image never changes. When you run a container, Docker adds one more layer on top — a writable one — and every file your process creates or modifies at runtime lands there, not in the image. That’s why two containers from the same image start identical and then diverge without stepping on each other: they share the read-only layers and each get their own private writable layer.

After enough years operating systems at scale, you learn to love anything that’s immutable and shareable. Read-only layers are both.

How layers get built (and why ordering matters)

Layers come from a Dockerfile. Roughly speaking, each instruction that changes the filesystem creates a new layer. Look at this example:

FROM python:3.12-slim

WORKDIR /app

# Dependencies change rarely — copy and install them first
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Application code changes often — copy it last
COPY . .

CMD ["python", "app.py"]

The instructions that produce filesystem layers are FROM (the base), COPY, and RUN. WORKDIR, CMD, ENV, and friends mostly add metadata rather than heavy filesystem content.

Now the part that pays off in build speed: Docker caches each layer and reuses it as long as the instruction and its inputs haven’t changed. When you rebuild, Docker walks the instructions top to bottom. For each one it asks, “have the inputs to this step changed since last time?” The moment the answer is yes, the cache is invalidated for that step and everything after it.

That’s why the ordering above isn’t arbitrary. Dependencies change rarely, so we install them early. Application code changes constantly, so we copy it last. If you flip that order — COPY . . before pip install — then every one-character change to your source invalidates the cache and forces a full reinstall of every dependency. Same Dockerfile, wildly different build times.

Let me show you how I’d actually verify the cache is working. Build once, change nothing, build again:

docker build -t myapp:1.4.2 .
# second run, no changes
docker build -t myapp:1.4.2 .

On the second build you’ll see CACHED next to the unchanged steps. Touch a source file, rebuild, and watch exactly which layer breaks the cache. That feedback loop is the fastest way to internalize how layering works.

Image vs. container: the difference that trips people up

People use “image” and “container” interchangeably, and it causes real confusion. Keep them separate:

  • An image is the static template — the stack of read-only layers plus metadata (default command, environment, exposed ports). It’s inert. It doesn’t run.
  • A container is a running (or stopped) instance of an image — that same layer stack with a writable layer on top and a process attached.

The analogy I use is a class and an object, or a program on disk versus a process in memory. One image can spawn many containers. Each container’s runtime changes live in its writable layer and vanish when you docker rm it — unless you deliberately persisted them with a volume.

This is also why “I’ll just edit the file inside the running container” is a trap. Those edits live in the ephemeral writable layer. The next container from that image won’t have them. If a change matters, it belongs in the Dockerfile.

Tags vs. digests: what “reproducible” really means

Every image has an ID — a sha256 content hash of its config. It also usually has one or more tags, which are human-friendly names like myapp:1.4.2 or python:3.12-slim.

Here’s the catch that burns people: a tag is a mutable pointer. myapp:1.4.2 points to a specific image today, but nothing stops someone from pushing a different image to that same tag tomorrow. The classic offender is latest, which means “whatever was pushed most recently” — not “the newest stable release.” Deploy myapp:latest twice a week apart and you may quietly get two different images.

A digest is the fix. It’s the immutable sha256 content address of the image. Same digest, same bytes, forever — if the content changed, the digest changes. You can pin to one like this:

docker pull python@sha256:3f8a...c19d

You can see the digest for what you pulled with:

docker images --digests

For anything I want to be genuinely reproducible — a base image in a Dockerfile, a deploy manifest — I lean on digests. Tags are fine for humans reading logs; digests are what you pin when “it built the same thing” actually matters. The tradeoff is readability: python@sha256:3f8a... tells you nothing at a glance, so a common pattern is to keep the tag in a comment for humans and the digest for the machine.

Registries: where images live

A registry is a server that stores and distributes images — Docker Hub, GitHub Container Registry, Amazon ECR, or a private one at registry.example.com. docker pull downloads an image from a registry; docker push uploads one.

docker tag myapp:1.4.2 registry.example.com/team/myapp:1.4.2
docker push registry.example.com/team/myapp:1.4.2

# on another host
docker pull registry.example.com/team/myapp:1.4.2

Because images are layered, registries and clients only transfer the layers they don’t already have. If your base image is already cached locally, a pull only grabs the layers on top. That shared-layer efficiency is a direct payoff of the model — the same reason a well-ordered Dockerfile builds fast makes pulls and deploys fast too.

Inspecting an image

You don’t have to take any of this on faith. Two commands show you the anatomy of an image directly.

docker history shows the layers and which instruction created each one, with sizes:

docker history myapp:1.4.2

This is the fastest way to spot a bloated layer — you’ll see exactly which RUN or COPY added 400 MB.

docker inspect dumps the full metadata as JSON — layers, environment, default command, entrypoint, exposed ports:

docker inspect myapp:1.4.2

When someone asks “what’s actually in this image,” these two commands answer it faster than any guessing.

Image hygiene: where people get burned

A few mistakes show up over and over. None are exotic, and all are avoidable once you understand layers.

  • Huge base images. Starting from a full ubuntu or python:3.12 when python:3.12-slim or a distroless base would do. Bigger images mean slower pulls, slower deploys, and a larger attack surface. Smaller images aren’t just faster — there’s less software inside for a CVE to land on.
  • Secrets baked into layers. This is the sharp one. If you COPY a .env file or pass a token via ARG and it lands in a layer, deleting it in a later layer does not remove it — the earlier layer still contains it, and anyone who pulls the image can extract it. Layers are additive history, not a live filesystem. Keep secrets out of the build entirely; use build secrets or inject them at runtime.
  • Relying on latest. It feels convenient and then it isn’t. Pin an explicit tag, and a digest when reproducibility matters.
  • One giant RUN that never cache-hits, or too many tiny layers. Group related commands so the cache is useful, but don’t chain unrelated steps that force needless rebuilds. Clean up package caches in the same RUN that created them, or that weight stays in the layer.

If you want a quick structural sanity check before you build, the free Dockerfile validator catches a lot of these footguns — unpinned base images, obvious secret-handling smells, ordering that will wreck your cache.

A quick note on AI here, since it comes up: a model is genuinely useful for drafting or reviewing a Dockerfile, but treat it as an assistant, not an authority. It’ll happily suggest something plausible and wrong, and — this part is non-negotiable — sanitize any real tokens or hostnames out before you paste a file into it.

Wrapping up

If you remember one thing, make it the mental model: an image is a stack of read-only layers, a container is that stack plus a thin writable layer, and every confusing Docker behavior — caching, bloat, leaked secrets, latest surprises — falls out of that structure once you see it. Build your Dockerfiles so the rarely-changing layers come first, pin digests when reproducibility counts, and keep your images small so your deploys are fast and your attack surface is boring.

Before your next push, run the Dockerfile through the free Dockerfile validator — it’s a five-second check that catches the mistakes above before they ship.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.