DEV Community

Cover image for Docker Images vs Containers, Explained
Shubham Sharma
Shubham Sharma

Posted on Originally published at techdevmantra.com

Docker Images vs Containers, Explained

If you are new to Docker, this is probably the first thing that trips you up: people say "image" and "container" like they mean the same thing, and they absolutely do not. Getting this one distinction straight makes almost everything else about Docker click into place.

Here is the whole idea in one sentence: an image is the read-only template, and a container is a running copy made from it. If you have written any code, it is the same relationship as a class and an object, or a recipe and the meal you cook from it. One image, many containers.

Info

Everything below was run on a real Docker Engine (Server 29.8.0). The image IDs, container IDs, and sizes are the genuine output. The tables are trimmed to the columns that matter here, so what you see on your own machine will have a few more columns.

One image, many containers

Let's prove the relationship instead of just asserting it. First, pull an image once:

docker pull nginx:alpine
Enter fullscreen mode Exit fullscreen mode

Now start three containers from that single image:

docker run -d --name c1 nginx:alpine
docker run -d --name c2 nginx:alpine
docker run -d --name c3 nginx:alpine
Enter fullscreen mode Exit fullscreen mode

There is still only one image on disk:

docker images nginx
Enter fullscreen mode Exit fullscreen mode
REPOSITORY   TAG      IMAGE ID       SIZE
nginx        alpine   72ba65eb42c1   104MB
Enter fullscreen mode Exit fullscreen mode

But there are three separate containers, each with its own container ID, all pointing back at that same image:

docker ps
Enter fullscreen mode Exit fullscreen mode
CONTAINER ID   IMAGE          NAMES
f291e8935b87   nginx:alpine   c3
dbbf21ab18e1   nginx:alpine   c2
01da24bab3c5   nginx:alpine   c1
Enter fullscreen mode Exit fullscreen mode

One image (72ba65eb42c1), three containers (01da..., dbbf..., f291...). That is the core fact: the image is the shared, unchanging original, and each container is an independent running instance of it. This is exactly why Docker is efficient. Ten copies of the same app do not mean ten copies of its files on disk. They share one image.

Tip

Two quick commands map cleanly onto the two concepts: docker images lists your images (the templates), and docker ps lists your running containers (the instances). If you ever lose track of which is which, that pair is the tell.

Each container gets its own writable layer

If they all share one read-only image, how can containers be different from each other? Because when a container starts, Docker adds a thin writable layer on top of the image, just for that container. Anything the container changes goes into its own layer and touches nothing else.

Watch it happen. Write a file inside c1:

docker exec c1 sh -c "echo 'written inside c1' > /note.txt"
docker exec c1 cat /note.txt
Enter fullscreen mode Exit fullscreen mode
written inside c1
Enter fullscreen mode Exit fullscreen mode

Now look for that same file in c2, which was started from the identical image:

docker exec c2 cat /note.txt
Enter fullscreen mode Exit fullscreen mode
cat: can't open '/note.txt': No such file or directory
Enter fullscreen mode Exit fullscreen mode

The file exists in c1 and does not exist in c2, even though both came from the same image. Each container's changes live in its own private writable layer. The image underneath never changed.

Warning

That writable layer is created and destroyed with the container. Run docker rm c1 and the note.txt you wrote is gone for good. This is the number one surprise for beginners: data written inside a container is not permanent. When you need data to survive, you use a volume, which is the subject of its own guide in this series.

So what is an image, really?

An image is not one big blob. It is a stack of read-only layers, each one a set of filesystem changes from the step that built it. You can list them with docker image history. It prints newest layer first, so here are the top few (the --format flag just trims it to size and command for readability):

docker image history nginx:alpine --format "{{.Size}}\t{{.CreatedBy}}" | head -6
Enter fullscreen mode Exit fullscreen mode
51.8MB   RUN /bin/sh -c set -x   && apkArch="$(cat ...
0B       ENV ACME_VERSION=0.4.1
0B       ENV NJS_RELEASE=1
0B       ENV NJS_VERSION=1.0.1
0B       CMD ["nginx" "-g" "daemon off;"]
0B       STOPSIGNAL SIGQUIT
Enter fullscreen mode Exit fullscreen mode

Each line is a layer. Some add real files and have a size (the 51.8MB package install), others just set metadata like an environment variable or the default command and cost nothing. This is only the top of the list; the base Alpine layer and the rest of the image sit below these, which is where most of the 104MB actually lives. Every container you run from this image shares all of those read-only layers and simply adds its own empty writable layer on top. That shared design is why the three containers above cost you 104MB of image once, not 104MB three times.

The mental model to keep

Line the two up side by side and the distinction sticks:

  • An image is built, versioned, and read-only. You create it with a Dockerfile, tag it, push it to a registry, and pull it. It is the same for everyone who pulls it.
  • A container is run, started, stopped, and thrown away. It is one live instance of an image, with its own ID, its own writable layer, and its own lifecycle.

The commands follow the same split. docker build and docker pull deal in images. docker run, docker stop, and docker rm deal in containers. And docker run is simply the bridge between them: it takes an image and produces a running container.

Once that lands, the rest of Docker is mostly detail. Next in the Docker Foundations series, put it to work by running your first containers hands-on, then move up to Docker Compose to run several at once.

Top comments (0)