Short answer: a Docker image is a read-only template: a stack of filesystem layers plus settings such as the command to run. A container is an instance of that image: the same layers plus a thin writable layer, its own settings and, while it runs, a process. One image can start many containers, as one program file starts many processes.
docker pull nginx:1.27 # the image: downloaded once
docker run -d --name site-a nginx:1.27 # container 1
docker run -d --name site-b nginx:1.27 # container 2, same image, separate state
docker images # lists images
docker ps -a # lists containersThe commands and storage details below follow Docker’s documentation on docs.docker.com. Docker was not run for this article, so the output blocks show the documented format, not a recorded session.
Docker image vs container at a glance
| Image | Container | |
|---|---|---|
| What it is | A read-only template | An instance of an image, running or stopped |
| Writable? | No: its layers never change once built | Yes: a thin writable layer that belongs to this container only |
| Made with | docker build from a Dockerfile, or docker pull | docker run, or docker create, from an image |
| Contains | Filesystem layers and settings: CMD, ENTRYPOINT, ENV, EXPOSE, the user | The image's layers, its own writable layer, runtime settings (name, ports, variables, mounts, limits) and, while running, processes |
| State | None: the same image is identical on every machine | created, running, paused, exited and a few more |
| Shared? | Its layers are shared by every container and every image built on it | Its writable layer and settings are its own |
| Where it lives | In a registry such as Docker Hub, and in the local image store | Only on the host that created it |
| Size on disk | The size of its layers, counted once | Only what it changed, shown by docker ps -s |
| Removed with | docker rmi, refused while a container uses it | docker rm; changes outside volumes are gone |
| Listed with | docker images | docker ps -a |
| Analogy | A class, a program file, a recipe | An object, a running process, the dish |
What a Docker image is
An image is a set of read-only filesystem layers and a small configuration file that says how to run it. Each Dockerfile instruction that changes files (RUN, COPY, ADD) adds a layer holding just those changes; instructions such as ENV, EXPOSE and CMD only change the configuration. Take this Dockerfile:
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY app.sh /usr/local/bin/app.sh
CMD ["app.sh"]docker build -t myapp:1.0 . turns it into an image, and docker history lists its layers, newest first:
IMAGE CREATED CREATED BY SIZE
3c1f0d2b9e7a 2 minutes ago CMD ["app.sh"] 0B
<missing> 2 minutes ago COPY app.sh /usr/local/bin/app.sh # buildkit 1.2kB
<missing> 2 minutes ago RUN /bin/sh -c apt-get update && apt-get ins… 9.8MB
<missing> 3 weeks ago ... the layers of debian:12-slim ...Layers are stored by content hash, so ten images built on debian:12-slim keep its layers on disk once, not ten times. A tag such as myapp:1.0 is a movable name pointing at an image; the digest (myapp@sha256:...) names exactly one image and never changes. Images follow the OCI image specification, which is why the same image runs under Docker, Podman, containerd and Kubernetes alike.
What a Docker container is
A container adds three things to an image: a writable layer, its own runtime settings, and, once started, a process isolated with the kernel’s namespaces and limited with cgroups. The image’s layers stay read-only underneath. When the container changes a file that came from the image, the storage driver first copies that file up into the writable layer and the container works on the copy (copy-on-write); a deleted file is only marked as deleted there. The image never changes, and other containers never see this one’s changes.
docker exec site-a sh -c 'echo "# extra" > /etc/nginx/conf.d/extra.conf'
docker diff site-aC /etc
C /etc/nginx
C /etc/nginx/conf.d
A /etc/nginx/conf.d/extra.conf
C /var/cache/nginx
A /var/cache/nginx/client_tempA is added, C changed and D deleted. That writable layer lives exactly as long as the container: stopping keeps it, docker rm deletes it. Data that must outlive the container, such as a database’s files or uploads, belongs in a volume or a bind mount, which sit outside the container’s layers.
CONTAINER ID IMAGE STATUS NAMES SIZE
4f1c2e9a7b3d nginx:1.27 Up 3 minutes site-a 1.09kB (virtual 192MB)
9d2e8b1a6c5f nginx:1.27 Up 3 minutes site-b 1.09kB (virtual 192MB)SIZE is the container’s own writable layer; virtual adds the image it shares with every other container of that image. Two containers of a large image cost that image once, plus what each one writes.
The lifecycle of each
| Stage | Image | Container |
|---|---|---|
| Comes into being | docker build, or docker pull from a registry | docker create, or docker run (create and start) |
| Named | docker tag myapp:1.0 registry.example.com/myapp:1.0 | --name at creation, docker rename later |
| In use | Read by containers; docker push shares it | running, paused (docker pause), restarting |
| Stopped | Images do not run, so they do not stop | exited after docker stop or when its process ends; docker start runs it again |
| Turned into the other | docker run makes a container from it | docker commit makes an image from it (a Dockerfile is the better habit) |
| Removed | docker rmi, once no container uses it | docker rm, after stopping |
The order of removal follows from that: an image cannot go while a container, even a stopped one, still uses it, so containers go first. Cleaning up both, and the docker system prune family, is covered in the Docker commands cheat sheet.
Commands for images and for containers
| Task | Image | Container |
|---|---|---|
| List | docker images | docker ps -a |
| Create | docker build -t myapp:1.0 . | docker run -d --name web myapp:1.0 |
| Inspect the settings | docker image inspect myapp:1.0 | docker inspect web |
| See what is inside | docker history myapp:1.0 | docker diff web, docker logs web |
| Get a shell | docker run --rm -it myapp:1.0 sh | docker exec -it web sh |
| Save to a file | docker save -o myapp.tar myapp:1.0 | docker export -o web.tar web |
| Remove | docker rmi myapp:1.0 | docker rm web |
| Remove all unused | docker image prune -a | docker container prune |
docker save and docker export look alike and are not: docker save writes an image with all its layers, tags and history, for docker load on another machine. docker export writes a container’s filesystem flattened into one tarball, without layers, history or settings such as CMD.
Container image vs base image
Every image you can run is a container image. A base image is the image another one starts from, the one named in its FROM line: in the Dockerfile above, debian:12-slim is the base image and myapp:1.0 the image built on it. Older Docker documentation was stricter: it called the image in FROM the parent image and kept “base image” for one built FROM scratch, the empty image. Current usage, Docker’s own included, calls the image in FROM the base image.
- Distribution images (
debian,ubuntu,alpine) give you a package manager and a shell.-slimvariants leave out documentation and extras. Alpine is the smallest of them but uses musl instead of glibc, which some prebuilt binaries and Python wheels do not support. - Language images (
python,node,golang) put a runtime on top of a distribution image, in full and slim variants. - Minimal images (
scratchand distroless images) hold almost nothing but your program: small and with little to attack, but no shell to debug in.
The base image is part of every image built on it, so its security fixes only reach you when you rebuild: docker build --pull -t myapp:1.1 . fetches the latest version of the base tag first. Pin the base to a version tag (debian:12-slim, not debian:latest) so a rebuild does not move you to a new major release by surprise.
Where Docker stores images and containers
docker info -f '{{.DockerRootDir}}' # usually /var/lib/docker
docker info -f '{{.Driver}}' # the storage driver, usually overlay2
docker system df # space used by images, containers, volumes, build cacheOn Linux with the default overlay2 driver, everything sits under /var/lib/docker: image layers and the containers’ writable layers in overlay2/, each container’s settings and its JSON log in containers/<id>/, and named volumes in volumes/. Docker installs that use the containerd image store keep image layers and snapshots in containerd’s own directory instead, by default /var/lib/containerd; there the storage driver that docker info reports is a containerd snapshotter such as overlayfs rather than overlay2, so its output shows which applies. Docker Desktop on Windows and macOS keeps all of it inside its Linux virtual machine.
Containers vs virtual machines
A container is an isolated process on the host’s kernel; a virtual machine boots a kernel of its own on virtual hardware. That makes containers start in moments and cost little more than the program itself, while a VM gives the harder isolation boundary and can run any operating system. They are usually combined, not chosen between: Docker runs inside a KVM virtual machine such as a Linux VPS exactly as on bare metal. The full comparison is in what virtualisation is, and how Docker’s one-process containers differ from system containers that behave like small VMs is in LXC vs Docker.
Three misunderstandings that cost people data
- “I changed the container, so the image has it.” It does not. The change lives in that container’s writable layer and is gone with
docker rmor when you recreate the container from a newer image. Put the change in the Dockerfile, or the data in a volume. - “Pulling a new image updates my container.” It does not. A container stays on the image it was created from; to update, pull, then remove and recreate the container (
docker compose up -ddoes both for Compose services). - “Stopping a container deletes it.” It does not, unless it was started with
--rm. Stopped containers keep their writable layer, their name and their image untildocker rm, which is whydocker ps -afills up over time.
Frequently asked
What is the difference between a Docker image and a container?
An image is a read-only template made of filesystem layers and settings such as the command to run. A container is an instance of an image: the image's layers plus its own writable layer, runtime settings and, while it runs, a process. One image can run as many containers at once.
Can a container exist without an image?
No. Every container is created from an image and needs its layers for as long as it exists, which is why docker rmi refuses to delete an image that any container, even a stopped one, still uses. Remove the containers first, then the image.
What is a base image?
The image another image starts from, named in the FROM line of its Dockerfile, such as debian:12-slim. Its layers become the bottom layers of your image, so its security fixes reach you only when you rebuild, for example with docker build --pull. An image built FROM scratch starts from nothing.
Where does Docker store images and containers?
On Linux under /var/lib/docker by default: image layers and the containers' writable layers in overlay2/, container settings and logs in containers/, named volumes in volumes/. docker info -f '{{.DockerRootDir}}' prints the location. Installs that use the containerd image store keep image layers in containerd's own directory, by default /var/lib/containerd.