Image vs container
- Image: a read-only template built from stacked layers; distributable and cacheable;
- Container: one running instance of an image, with a writable layer on top.
Why layering helps
Shared base layers are reused across images, saving pull and storage. Changing one line usually rebuilds only the top layer.
Two pitfalls
- Writing data into the container: it vanishes when the container is removed; persist with volumes;
- Images grow unbounded: keep build tools in the image and it balloons; use multi-stage builds to ship only what runs.
Real-world cases: three costs of confusing images with containers
- Deleting a container and expecting free disk: removing a container drops only its writable layer; the image still occupies space. Distinguish
docker rm(containers),docker rmi(images) anddocker system prune. - Expecting in-container edits to persist: changes live only in the writable layer and vanish on recreate. To keep them, put them in the Dockerfile and rebuild the image.
- The same image behaving differently: identical image but different env vars, mounts and networks — the difference is runtime, not the image. Compare
docker inspectoutput before rebuilding.
FAQ
Are images read-only? Yes — an image is a read-only template and a container adds a writable layer on top. Why do containers start faster than VMs? No full OS boot; it just starts a process over an added writable layer. How many processes per container? Usually one main process; split multiple processes into separate containers for independent scaling and restarts. Do image layers waste space? Layers are shared between images, so with a common base the total is far below the sum of image sizes.
Slimming an image, highest impact first
- Change the base image:
alpineordistrolessoften saves hundreds of MB, but mind musl versus glibc differences; - Multi-stage builds: leave build dependencies in the builder stage and copy only artefacts into the runtime — often an order of magnitude smaller;
- A real .dockerignore: exclude
node_modules,.gitand build caches to keep the context small and layers stable; - Order instructions by change frequency: copy the dependency manifest and install before copying source, so caching actually hits;
- Clean up install caches: delete
/var/lib/apt/listsafterapt-getand prune npm caches, in the same layer as the install.
Three runtime details often missed
- Signal handling: the app must handle
SIGTERMand shut down gracefully or rolling updates drop requests — use exec form (CMD ["node","server.js"], not shell form); - Run as non-root: set
USERto a non-privileged account to shrink the blast radius; - Read-only filesystem: run with
--read-onlywhere possible and mount explicit volumes for writes.
Common misconceptions
- Baking config into the image: inject environment-specific settings via env vars or mounts so one image deploys everywhere;
- Using the latest tag: nothing is traceable and rollback is guesswork — use immutable tags (version or commit hash);
- Running several processes in one container: process management and log collection get complicated; split them.
Volumes and lifecycle
- The container filesystem dies with it: anything durable must live in a volume or bind mount, or a recreate wipes it;
- Anonymous volumes get forgotten: those declared by
VOLUMEcan linger after the container is gone — clean up withdocker volume prune; - Bind mounts depend on host paths: deployments across machines fail when the path is missing, so named volumes are safer in production;
- Volumes do not follow the image: updating an image does not migrate data in a volume; run migrations explicitly.
Where to start troubleshooting
docker inspectfor config and mounts,docker logsfor app output,docker execto look inside;- Read the exit code first:
137usually means OOM-killed,139points at a segfault; - If the image has no shell (distroless), rely on logs and probes rather than bolting one in.
Image scanning and compliance
Wire image scanning into CI so base image and dependency vulnerabilities are checked right after build, with a gate by severity — block merges on critical findings. Rebuild base images regularly for upstream security fixes; an image left untouched accumulates known vulnerabilities even when your code did not change.