04 — Docker Basics: Image, Container, Dockerfile
I kept losing the thread between Dockerfile, image, and container until I lined them up as one pipeline. The model that clicked is almost a cooking metaphor: a Dockerfile is the recipe, an image is the frozen dish you can ship, a container is one serving of that dish on a plate right now. [1][4]
The three are a strict pipeline — each one produces the next — and that ordering is what unstuck me. A Dockerfile is built into an image; an image is run into a container. You can't run a Dockerfile and you can't build a container, and once that sounds obvious, the rest of Docker's command surface falls into place [1].
The three-part pipeline
Each artifact has one job, and the command that produces it is named after the destination, not the source — docker build makes an image, docker run makes a container [1][4].
The Dockerfile: a recipe of layer-producing instructions
A Dockerfile is a plain text file of instructions, each one producing a new layer in the resulting image [2]. A handful of instructions cover most real Dockerfiles:
FROM node:20-alpine # base image — the bottom layer
WORKDIR /app # set the working directory for later steps
COPY package*.json ./ # copy host files into the image
RUN npm ci # run a build command (creates a layer)
COPY . . # copy the rest of the source
EXPOSE 3000 # document which port the app listens on
CMD ["node", "server.js"] # the process to start when the container runsTwo instructions I had to learn to tell apart: RUN executes at build time (it bakes results into the image), while CMD is what the container runs at startup [2]. Conflating them is the classic beginner mistake — putting npm start in a RUN will build fine and then do nothing at runtime because there's no CMD.
The image: a frozen, layered snapshot
A Docker image is the read-only, layered artifact the build produces — application code, runtime, libraries, and config, all stacked [4]. It's immutable; once built, it doesn't change, which is what makes it safe to ship. The same image run on my laptop, in CI, and in production is, by construction, the same artifact [4].
Images are addressed by name and tag: node:20-alpine, my-app:1.2.0, postgres:16. The tag is just a label — :latest is the default when I omit one, and it's the most misleading label in Docker, because "latest" doesn't mean newest-built, it means whatever was last pushed under that name [4]. I treat :latest as a footgun in any non-trivial workflow.
The container: a running instance
A container is a live process — an image plus a writable scratch layer on top, plus a running instance of the CMD [1]. The key behaviors that follow from that definition:
- One image can produce many containers. docker run my-app five times gives five independent processes.
- Writes inside the container land in the writable layer and are gone when the container is removed. Persistence needs volumes, covered in a later post.
- Stopping a container (docker stop) freezes the process; the writable layer survives until I docker rm the container. Removing the container is what discards its scratch state [1].
The command surface, in pipeline order
The handful of commands I use daily maps cleanly onto the pipeline. Memorize them in build → run → manage order and the surface stops feeling sprawling [1]:
# images
docker pull nginx # fetch an image from a registry
docker build -t my-app:1.0 . # build an image from the Dockerfile in .
docker image ls # list local images
docker image rm my-app:1.0 # delete an image
# containers
docker run -d -p 8080:3000 --name api my-app:1.0 # run a container
docker container ls # list running containers
docker container ls -a # include stopped ones
docker container stop api # stop a container
docker container rm api # remove a stopped containerThe -d flag means detached (background), -p host:container maps a host port to a container port, and --name gives the container a stable name I can use in later commands instead of copying a random hash [1]. That's roughly 80% of day-to-day Docker.
How I use this
The vocabulary pipeline is the lens I use to read error messages and plan changes. When docker run complains image not found, I know I either forgot to docker build first or mistyped the tag — the source artifact doesn't exist. When I need to change app behavior, I edit the Dockerfile and rebuild, because the image is immutable and patching a running container's files is a lie that disappears on the next redeploy. And when a container keeps restarting, I reach for docker logs api to read what the CMD's process is printing before it dies. Naming the artifact in the pipeline — recipe, snapshot, instance — points at the right fix almost every time.
References
[1] Docker, Inc., "Docker concepts — Running containers," Docker Docs, 2024. [Online]. Available: https://docs.docker.com/get-started/docker-concepts/running-containers/
[2] Docker, Inc., "Dockerfile reference," Docker Docs, 2024. [Online]. Available: https://docs.docker.com/engine/reference/builder/
[3] Docker, Inc., "Dockerfile best practices," Docker Docs, 2024. [Online]. Available: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
[4] Amazon Web Services, "What's the difference between Docker images and containers?," aws.amazon.com, 2024. [Online]. Available: https://aws.amazon.com/compare/the-difference-between-docker-images-and-containers/
Knowledge check · Question 1 of 5
Which sequence correctly produces a running container from source?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!