To try the hands-on steps you will need Docker installed, either Docker Desktop or Docker Engine. Everything here is free.
To try the hands-on steps you will need Docker installed, either Docker Desktop or Docker Engine. Everything here is free.
A container packages your app together with everything it needs to run, so it behaves the same on your laptop, on the CI runner, and in production. Docker is the most common tool for building and running containers. You write a small file called a Dockerfile, build it into an image, and run that image as a container. It is the cure for the oldest excuse in software: it works on my machine.
For the college passout or career switcher who has heard that Docker is essential and wants to actually build and run a container, not just nod along when someone says the word.
A developer finishes a feature, it runs perfectly on their machine, and they hand it off. On the next person is laptop it crashes, because they have a different version of the language, a missing library, or a setting nobody wrote down. Multiply that by a team and a production server, and you get the endless it works on my machine argument. Containers end that argument by shipping the machine along with the code.
The whole idea starts with one small text file. Here is a real Dockerfile for a Node app, the recipe that describes how to package it. Read it as a list of steps a fresh machine would follow.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]FROM picks a small base image with Node already installed. WORKDIR sets the folder. COPY brings files in. RUN installs dependencies. EXPOSE documents the port. CMD is the command that starts the app when the container runs.
Part 7 built and tested your code on a clean CI runner. Docker is how you make sure the thing that ran there is the exact same thing that runs in production, byte for byte. That is why containers sit at the center of almost every modern pipeline.
Containers are not virtual machines
People often meet containers by comparing them to virtual machines, and the difference is worth getting right because it explains why containers took over. A virtual machine emulates a whole computer, including its own full operating system, on top of your real one. A container does not. It shares the host is operating system kernel and only packages the app and its dependencies. That makes a container start in seconds and weigh tens of megabytes, where a virtual machine takes minutes to boot and weighs gigabytes.
| Aspect | Container | Virtual machine |
|---|---|---|
| Startup | Seconds | Minutes |
| Size | Tens of MB | Several GB |
| Operating system | Shares the host kernel | Full OS per machine |
| Overhead | Low | High |
| What it boots | A single process | A whole system |
Image and container: blueprint and instance
Two words get confused constantly, so pin them down. An image is the packaged, frozen blueprint you build from a Dockerfile. A container is a running instance of that image. One image can start many containers, the same way one class can create many objects, or one recipe can make many identical cakes. You build an image once and run it as many containers as you need, on any machine that has Docker.
Your first image, built and run
With the Dockerfile from the top of this part sitting next to your code, two commands take you from files to a running app. Build turns the Dockerfile into an image. Run starts a container from it.
$ docker build -t myapp:1.0 .
[+] Building 12.4s (10/10) FINISHED
=> [1/5] FROM docker.io/library/node:22-slim
=> [4/5] RUN npm ci --omit=dev
=> naming to docker.io/library/myapp:1.0
$ docker run -d -p 3000:3000 myapp:1.0
7c9f2a1b8e4d
$ docker ps
CONTAINER ID IMAGE PORTS NAMES
7c9f2a1b8e4d myapp:1.0 0.0.0.0:3000->3000/tcp nifty_babbage
$ curl -I localhost:3000
HTTP/1.1 200 OKThe -t myapp:1.0 names and tags the image. The -d runs the container in the background, and -p 3000:3000 publishes the container’s port 3000 to the same port on your machine so you can reach it. That last flag is the one beginners forget.
You run the container without the port flag and then cannot reach it:
$ docker run -d myapp:1.0$ curl localhost:3000curl: (7) Failed to connect to localhost port 3000: Connection refusedThe app is running fine inside the container, but the container is a sealed box. Nothing reaches its ports from outside unless you publish them. Add
-p 3000:3000 and the connection works. The lesson: a container is isolated by default, and you open exactly the doors you need with -p. This same isolation is what makes containers safe to run side by side.| Command | What it does |
|---|---|
docker build -t name:tag . | Build an image from the Dockerfile here |
docker run -d -p 80:3000 name | Run a container and publish a port |
docker ps | List running containers |
docker logs name | Read a container’s output |
docker exec -it name sh | Open a shell inside a running container |
docker stop name | Stop a running container |
docker pull / push | Get or share an image via a registry |
Data disappears unless you tell it not to
Here is the default that surprises every beginner. A container is disposable. When you stop and remove it, everything written inside it is gone. Start a database in a plain container, add data, remove the container, and the data vanishes with it. This is not a bug. Containers are meant to be replaceable, so anything you want to keep must live outside the container.
The fix is a volume, a folder on the host machine that the container writes to. You attach it with another flag, -v, and now the data survives the container being destroyed and recreated. The rule of thumb is simple: containers hold your running code, volumes hold your data, and you never store anything precious inside a container’s own filesystem. Getting this wrong is behind a lot of lost-data horror stories from people is first month with Docker.
Layer caching: why the order in a Dockerfile matters
Look again at the Dockerfile. It copies package.json and installs dependencies before it copies the rest of the code. That order is deliberate, and it is one of the most useful things to understand about Docker. Each instruction builds a layer, and Docker caches layers. When you rebuild, it reuses every layer up to the first line that changed, then rebuilds from there.
Dependencies change rarely, but your code changes constantly. By copying the dependency file and running the install first, Docker can reuse that expensive install layer on every rebuild where only your code changed, and installs can take minutes. Copy everything at once instead, and every one-line code change throws away the cache and reinstalls all dependencies from scratch. The difference is a build that takes five seconds versus five minutes. Order your Dockerfile from least-changing to most-changing and your builds stay fast.
When a container will not start: logs and exec
Sooner or later you run a container and it does not stay up. The site is unreachable and docker ps shows nothing, because the container already died. This is the most common Docker moment in a real job, and the way out is always the same two commands. First, docker ps -a shows stopped containers too, including the exit code. Then docker logs shows what the app printed before it quit.
$ docker ps -a
CONTAINER ID IMAGE STATUS NAMES
b2c3d4e5f6a7 myapp:1.0 Exited (1) 3 seconds ago sad_hopper
$ docker logs sad_hopper
Error: Cannot find module 'express'
at Function._resolveFilename (node:internal/modules)The exit code 1 next to Exited tells you the app crashed rather than shut down cleanly, and the log names the cause: a dependency the image does not have, usually because it was missing from the dependency file the Dockerfile installs. The container was built exactly as instructed. The instructions were incomplete. When you need to look inside a container that is still running, docker exec -it name sh drops you into a shell inside it, where the Linux commands from Part 4 all work, so you can list files and check config from the inside.
Master one container before Compose
Beginners often jump straight to Docker Compose and multi-container setups because a tutorial showed a whole stack at once. Skip that at first. If you cannot confidently build one image, run it, publish a port, read its logs, and shell into it, adding orchestration on top just hides the parts you have not learned. The genuine skill is understanding a single container: what an image is, how a container runs, where its data goes, and how it talks to the outside world. Once that is solid, Compose is a small step, and Kubernetes in the next part makes far more sense.
One more honest opinion: use small official base images like the slim variants, not the biggest one you can find. A smaller base means faster builds, smaller downloads, and fewer security holes to worry about. It is the cheapest good habit in the whole topic.
What is the difference between an image and a container? The clean answer: an image is the read-only blueprint you build from a Dockerfile, and a container is a running instance of that image. One image can spawn many containers. Add that images are built and shared through a registry, while containers are the live processes doing the work, and you have shown you understand the lifecycle, not just the words.
Almost every team ships containers now, so on your first week you will build an image, run it locally, and read its logs to debug something. Being able to say the container is running but the app inside it is crashing, here is the log line, or that endpoint is unreachable because the port is not published, marks you as someone who can work with the stack instead of being blocked by it. These are small skills that make you productive immediately, and none of them require Kubernetes.
Free, about twenty minutes. Install Docker Desktop or Docker Engine, then run a ready-made image with
docker run -d -p 8080:80 nginx and open localhost:8080 in a browser to see the nginx welcome page. Then run docker ps to see it, docker logs on its name to read its output, and docker stop to shut it down. How to check you did it right: the browser shows the nginx page while the container runs, and the page stops loading once you stop the container. You just pulled, ran, inspected, and stopped a container without writing any code.Always tag your images with a real version like
myapp:1.0 rather than relying on the default latest. The word latest is not magic, it is just a name that moves, and two servers pulling latest a week apart can quietly run different builds. Pin a version in your pipeline and in production so you always know exactly which image is running. When something breaks, being able to say production is on 1.4 and staging is on 1.5 turns a guessing game into a five second answer.Docker questions that come up early
Do containers replace virtual machines?
Not entirely. They solve different problems. Containers are lighter and perfect for packaging apps, while virtual machines give stronger isolation and can run different operating systems. In practice, containers often run inside virtual machines in the cloud, so you use both, at different layers.
What is a registry?
A place to store and share images, like GitHub is for code. Docker Hub is the best known public one, and companies run private registries too. You push an image to a registry and others pull it, which is how the same image reaches the CI runner and the production server.
Is Docker the only container tool?
No, but it is the one to learn first because it is everywhere and its file format became the standard. Podman and containerd are alternatives that use the same images. Learn Docker and the concepts transfer directly, which is the pattern across this whole series.
Why is my image so huge?
Usually because of a large base image or copying files you do not need. Switch to a slim base, add a .dockerignore file to leave out junk, and for compiled languages use a multi-stage build so the final image holds only what runs. A lean image builds faster and has fewer vulnerabilities.
You can now package an app so it runs the same everywhere. Next up is Part 9 on Kubernetes, the system that runs many containers across many machines and keeps them healthy without you watching them by hand.
New here? Start with Part 3 on the DevOps loop and Part 7 on CI/CD, which builds these images automatically. The Cloud for Beginners series pairs well for where containers run.
References
Docker: Get started
Docker: Dockerfile best practices
Docker: Volumes and persistent data


DrJha