SassTurf
BlogBuilding
Building

Docker Image vs Container: What's the Difference? (2026)

Docker image vs container explained in plain English — the class-vs-instance analogy, layers, registries, and what actually happens when you run docker run.

Shubham Soni
Shubham Soni
Jul 14, 2026 · 10 min read
Table of contents8 sections
  1. 01The Night I Killed My Container and Thought I’d Killed My Work
  2. 02The Analogy That Actually Sticks: Class vs Instance
  3. 03What’s Actually Inside a Dockerfile → Image
  4. 04docker run: What Actually Happens
  5. 05One Image, Many Containers — This Is the Whole Point
  6. 06Registries: Where Images Actually Live
  7. 07The Mistake Everyone Makes at Least Once
  8. 08The Bottom Line

The Night I Killed My Container and Thought I’d Killed My Work

It was 2021, before Clickly, before I’d failed at enough SaaS ideas to write a whole blog about it. I was doing a contract gig, and the client wanted their little internal tool “containerized” before I handed it off. I’d used Docker exactly twice before — copy-pasted a docker run command from a tutorial, watched something spin up, felt like a wizard, moved on. I’d never actually had to think about what I was doing.

So that night I built an image, ran it, and it worked. Then, because I’m me, I started poking at it — shelled into the running container with docker exec, installed a missing system package by hand, tweaked a config file directly inside it, got it running exactly how the client wanted. Beautiful. I closed my laptop, proud of myself, and went to bed.

Next morning, demo day, I ran the container again for a live call. None of it was there. The package I’d installed — gone. The config tweak — gone. It was like the whole night had never happened. I genuinely thought I’d broken something, that Docker had corrupted a file, that I was about to explain to a client why their tool was broken on camera.

Nothing was corrupted. I just didn’t understand the one thing this whole post is about: I had edited a container, not the image. The container was gone the moment I stopped it, and everything I’d changed inside it went with it. The image — the actual blueprint — never knew any of that had happened. I’d been patching the meal, not the recipe.


The Analogy That Actually Sticks: Class vs Instance

If you’ve written any object-oriented code, you already understand Docker images and containers. You just don’t know it yet.

A Docker image is a class. It’s a definition — a static, versioned description of what something looks like and what it contains. It sits on disk (or in a registry) doing nothing. It has no running processes, no memory usage, no state. It’s just bytes describing a thing.

A container is an instance of that class. The moment you say docker run, Docker takes the class and instantiates it — allocates it a slice of filesystem, memory, and process space, and starts executing whatever the image says to execute. You can create one instance. You can create fifty. Each one is independent — kill one, the other forty-nine don’t notice. Change something inside one, the class definition it came from doesn’t change at all.

If you’re not a coder, the recipe-and-meal version works just as well: the recipe (image) is the fixed set of instructions — 200g flour, 2 eggs, bake at 180°C. The meal (container) is what happens when someone follows it in a kitchen. Cook that recipe once, or a hundred times in a hundred kitchens — burn one pan of cookies and the recipe card is still sitting there, untouched.

That’s the whole idea. A Docker image is a frozen, versioned blueprint. A Docker container is a live, disposable, running copy of it. Nearly every confusing Docker moment I’ve ever had — and there have been plenty — traces back to forgetting which one I was actually looking at. (Once the concept clicks, the next question is when to actually reach for it — I listed the real Docker use cases for a solo founder.)


What’s Actually Inside a Dockerfile → Image

The image doesn’t appear from nowhere. You write a Dockerfile — a short, literal recipe — and Docker turns it into an image with docker build.

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]

Each line in that file becomes a layer. FROM pulls in a base layer (a minimal OS plus a language runtime). COPY and RUN each stack another read-only layer on top. Docker glues these layers together into one image using a union filesystem, so the container that eventually runs from it just sees one coherent set of files, even though under the hood it’s five or six stacked, immutable slices.

This is why layer order matters more than people expect. Docker caches each layer and only rebuilds it (and everything after it) if that layer’s inputs changed. Notice the Dockerfile above copies package.json and runs npm install before copying the rest of the app code. That’s deliberate: your code changes constantly, your dependencies change rarely. Order it this way and a routine code change reuses the cached dependency-install layer instead of reinstalling every package from scratch. Get the order backwards and every build reinstalls everything, and you’ll wonder why CI takes four minutes for a one-line fix.

The image that comes out the other end of docker build is just those layers, addressed by an ID (and usually a human tag like myapp:1.0). It is completely inert. It does nothing until you run it.


docker run: What Actually Happens

Here’s the part that finally made everything click for me, months after that failed demo.

docker build -t clickly-api:1.0 .
docker run -d --name api-1 -p 3000:3000 clickly-api:1.0
docker run -d --name api-2 -p 3001:3000 clickly-api:1.0
docker ps

That docker build produces exactly one image on my disk. Nothing is running yet. Each docker run does the same thing twice: takes the immutable image, adds one more layer on top — a writable one, unique to that container — and gives the whole stack its own isolated process tree, network namespace, and filesystem view. docker ps now shows two separate, live containers, api-1 and api-2, both built from the exact same image, both unaware the other exists.

That writable layer is the part I destroyed the night of the failed demo. Everything I changed inside the running container lived in that one throwaway layer. Stop the container, and by default that layer — and every edit I made inside it — disappears with it. The image underneath, the actual class definition, was never touched. If you want a change to survive, it has to go into the Dockerfile and get rebuilt into a new image. Editing a running container by hand and expecting it to stick is the single most common trap I see people repeat, myself included, more than once.


One Image, Many Containers — This Is the Whole Point

The reason this distinction exists isn’t academic. It’s the entire reason Docker is useful.

ImageContainer
What it isa frozen, versioned blueprinta live, running instance of that blueprint
Where it livesa registry, or your local diska host machine — your laptop, a server
Storagestacked, read-only layersimage layers + one writable layer
Statenone — it’s static byteshas real state: files, memory, running processes
Lifecyclebuilt once with docker build, then taggedstarted, stopped, restarted, deleted — usually many times
How many existexactly one per buildzero, one, or a thousand, all from that one image

Because an image is static and a container is disposable, you can scale horizontally without duplicating any actual “stuff.” Need to handle more traffic? Don’t build ten separate copies of your app — spin up ten containers from the same image. Each one is cheap, fast to start (seconds, not the minutes a full virtual machine would take — here’s the full Docker vs VM difference), and isolated enough that one crashing doesn’t take down the rest. If a container misbehaves, you don’t debug it — you kill it and start a fresh one from the same trusted image.

This is also, by the way, exactly the layer where Kubernetes and Docker Swarm live — deciding how many containers to run from an image and where. I’ve written about Docker vs Kubernetes and Docker Swarm separately, and the short version of both posts is: as a solo founder, you almost never need to make that decision by hand. But you do need to understand images and containers first, because both of those tools are just fancy automation for “run N containers from this one image, and keep them alive.”


Registries: Where Images Actually Live

An image is useless sitting only on your laptop — the whole value of Docker is that the same image runs identically on your machine, your teammate’s machine, and a production server. That requires somewhere to store and share it: a registry.

The default public one is Docker Hub — think of it as GitHub for images. docker pull postgres:16 reaches out to Docker Hub, grabs a ready-made Postgres image someone else built and published, and now you have it locally. docker push does the reverse — ship your own built image up so anyone (or just your own deployment pipeline) can pull it down later.

Two limits are worth knowing before they surprise you mid-deploy. Docker Hub rate-limits pulls: 100 per 6 hours for anonymous requests, 200 per 6 hours on a free, logged-in Personal account. It rarely bites a solo dev clicking around, but I’ve watched a CI pipeline fail with “too many requests” because it pulled a base image on every run without logging in — logging in, or caching the image, fixes it instantly. Second, the free tier gives you exactly one private repository; public repos are unlimited.

Tags are how you version an image without renaming anything: node:20-alpine, node:20-alpine3.19, node:latest. Never actually ship :latest to production, by the way — it’s a moving target that silently points at whatever was pushed most recently, and “it worked yesterday” becomes a very bad Tuesday.


The Mistake Everyone Makes at Least Once

A few commands are worth knowing cold, because the naming genuinely trips people up:

  • docker images lists your images. docker ps lists your running containers. docker ps -a lists every container, running or stopped — the stopped ones are still sitting on disk, quietly, until you clean them up.
  • docker rm <container> deletes a container. docker rmi <image> deletes an image. One letter of difference, completely different blast radius — delete the wrong one at 11 PM and you’ll understand why I’m mentioning it.
  • Stopped containers and unused images pile up fast if you rebuild often. docker system prune clears the debris — dangling images, dead containers, unused networks — worth running every so often, because a laptop full of forgotten containers from six abandoned side projects (I speak from experience) eats gigabytes for no reason.

And the big one, the one that cost me a demo: if you need a change to survive, it belongs in the Dockerfile, followed by a rebuild — not typed by hand into a running container’s shell. The container is the meal. You don’t edit the recipe by seasoning one plate.


The Bottom Line

Once the class-and-instance framing clicks, Docker stops being a pile of memorized commands and starts being obvious. An image is a frozen, versioned description of your app. A container is what happens when you actually run that description — disposable, isolated, cheap enough to spin up ten of without a second thought. Everything else — layers, registries, tags, docker system prune — is just plumbing underneath that one idea.

I lost a night of edits before I understood this, and I’ve watched other engineers make the exact same mistake since. It’s a cheap lesson if you learn it here instead of on a client call. Build the image once, run it as many times as you need, and stop trying to hand-patch the meal. (If you’re stitching this into the rest of a shoestring stack, the broke solopreneur’s survival guide is the wider map.)

This is the Broken Engineer Guide — I over-engineer everything, fail at business, and write down the scars so yours heal faster. Go build the image, then go find someone to run it for.

Shubham Soni
Written by
Shubham Soni

A decade building, launching, and occasionally breaking SaaS products. I write SassTurf to share what actually moved the needle — free, no fluff.

Keep reading

Building

LangChain Alternatives That Actually Earn Their Place (2026)

9 min read
Building

Kubernetes Deployment: A Founder's Guide to the One File You'd Actually Write (2026)

9 min read
Email Marketing

Out of Office Email Templates That Don't Sound Like a Robot (2026)

8 min read

Enjoyed this? Get the next one.

One useful SaaS essay in your inbox each week. No fluff, unsubscribe anytime.