
Docker, explained simply, is a tool that packages your app together with everything it needs to run, so it behaves the same on your laptop, in testing and on a production server. It exists to solve a familiar problem: the code is identical, but the result is not.
This guide starts with that problem, then walks through how Docker works inside. It covers the Dockerfile, layered images, containers, registries and Compose. Then it follows one commit to production and ends with three mistakes worth avoiding early.
- Apps break on servers because the environment differs, not the code.
- Docker packages the app plus its runtime, libraries, config and OS files into one container.
- A Dockerfile is the recipe. It builds a layered image that caches well.
- The same image moves unchanged from development through CI to production.
- Keep images slim, store data outside containers, and don't run as root.
- 0:00Intro
- 0:23Works on my machine
- 1:15Ship the whole box
- 2:08One environment, every stage
- 3:00The Dockerfile is the recipe
- 3:53Images are built in layers
- 4:46Run an image, get a container
- 5:37Registries store and share images
- 6:29Compose runs the whole app
- 7:19Following one commit to production
- 8:09Docker shows up everywhere
- 8:56Three mistakes that bite later
- 9:48What we covered
- 10:22Package the environment, not just code
Why does my app work locally but break on the server?
The tests pass on your laptop. You deploy, and the server throws an error you have never seen. Your code is identical in both places, but everything around that code is not. The cause usually hides in small details: a different runtime version, a missing system library, or a config file that only ever existed on your laptop.
These hidden differences build up quietly. Your laptop has collected tools and settings over months. The server was set up separately, by someone else, at a different time, and nobody wrote all of it down. Neither machine is wrong. They have simply drifted apart.
The cost is lost hours. Instead of shipping features, the team compares machines line by line, looking for the one difference that matters. So the real problem isn't your code. It's the environment, and that is what Docker is designed to fix.
- Different runtime version
- Missing system library
- Config that only exists on one machine
What is Docker, and why does it exist?
Docker packages your app and its dependencies into a portable unit called a container. Think of a shipping container: whatever is inside, every ship and crane handles it the same way. Your container holds the app code, the runtime that executes it, the libraries it depends on, the config it reads, and the base OS files it expects to find.
A container is lighter than a virtual machine. A VM boots a whole operating system of its own. A container skips that step and shares the host, so it starts quickly and uses fewer resources. Because the contents travel together, your laptop, a teammate's laptop and a cloud server all run the same thing.
The deeper reason Docker exists is consistency. You build the package once and promote that same package from stage to stage. It goes from the dev laptop to CI tests, then to staging, then to production, unchanged. What you tested is exactly what you ship. Because each image is a fixed, versioned package, a bad release can be rolled back by running the previous image again.
- Portable: runs the same on any machine with Docker
- No drift: tested and shipped versions are identical
- Easy rollbacks: redeploy the previous image
What is a Dockerfile?
A Dockerfile is a plain text file that lists, step by step, how to assemble your app's environment. Instead of setting up a server by hand, you write the setup down once, as code. The example below starts with FROM, which picks a base image, here Node on Alpine Linux. WORKDIR sets the working folder.
Next, the package files are copied, npm ci installs dependencies, and the rest of the code is copied in. Finally, CMD says how to start the app. Docker runs these instructions in order, the same way every time. Each line is one repeatable step, so the next machine never misses something you forgot to install.
The Dockerfile lives in your repository next to your code. Environment changes get reviewed in pull requests and tracked in history like any other change. Your setup stops being knowledge in one person's head and becomes a file anyone on the team can read.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]
How do Docker image layers and caching work?
Each instruction in a Dockerfile produces a layer, and an image is a stack of those layers. Docker caches layers and reuses them whenever nothing behind them has changed. This is what makes rebuilds fast.
Here is a worked example. You edit server.js and rebuild. The base image, node:20-alpine, comes straight from cache. The npm ci step is cached too, because the package files didn't change. Only the COPY step that brings in your code is rebuilt, and the new image, myapp:v2, is ready.
This is why order matters. The Dockerfile installs dependencies before copying your code, so the slow install stays cached while you edit code all day. Put rarely changing steps first and code last. Layers also save space: several images built on the same base store and download that base only once.
- Rarely changing steps first, code last
- Images on one base share its layers
What is the difference between an image and a container?
An image is the blueprint. A container is a running copy of it. It works like a class and its objects. The image stays read-only, and each docker run creates a new live, isolated process on the host. One run might start a web container, and a second run starts a worker container from the same image.
Each container is isolated. It sees its own file system, its own network interfaces and its own process list, so one container can't simply read another's files. All containers on a host share the host kernel instead of booting their own operating system.
That shared kernel is why containers are light. There is no guest operating system to start, so a container launches about as fast as a normal process. You get isolation without the weight of a virtual machine.
How do Docker images get to the server? Registries
You don't copy image files around by hand. You use a registry, such as Docker Hub, which stores images so any machine can fetch them. A registry does for images what a Git host does for code. A CI runner builds the image and runs docker push. Staging and production servers then run docker pull for the very same image.
Tags give each build a name, such as myapp:v2, so every server knows exactly which image to run, and older versions can be pulled back when needed. Private registries keep internal images behind access control. Docker Hub offers private repositories, and cloud providers run their own registries. The registry is the hand-off point between building an image and running it.
What is Docker Compose used for?
Real apps are rarely a single container. You might have a web app, a database and a cache. Docker Compose runs multi-container apps together from one file, usually called compose.yaml, which lives in your repo. In the example stack, the browser talks to web, a Node.js app. Web talks to cache, running Redis, and to db, running PostgreSQL, all on one shared network.
Running docker compose up -d starts everything in the background. Compose creates the network, then starts the db, cache and web containers. Running docker compose down removes the stack again. With one command, a new teammate can run the full app locally without a long setup guide.
docker compose up -d
From one commit to production, and where Docker is used
Here is how the pieces combine in a typical pipeline. You run git push. CI runs docker build from your Dockerfile, and tests execute inside a container made from that image. If they pass, the image is pushed to the registry with a version tag. The production host pulls that exact tag, the new container starts, and the old one stops.
The key detail is that the image is built exactly once. Every step reuses it, so the server runs what CI already tested. That closes off the "works on my machine" problem.
Because one image format works everywhere, Docker shows up across the software lifecycle. CI pipelines use it for clean, repeatable test runs, microservices ship as their own containers, local development matches production, and the cloud runs images on managed container services and Kubernetes. New developers can also clone the repo, run one command, and have the app working on day one.
- CI pipelines
- Microservices
- Local development
- Cloud services and Kubernetes
Common Docker mistakes and how to fix them
Docker is easy to start with and just as easy to misuse. Most problems come from habits, not from Docker itself. The first mistake is bloated images. A heavy base and leftover build tools make images huge, so pulls and deploys crawl. Fix it with a slim base, multi-stage builds and a .dockerignore file.
The second mistake is keeping data inside the container. Remove the container and that data is gone. Use volumes or a managed database instead. The third is running as root. If an attacker breaks out of the container, they get more power on the host. Add a USER line and run with least privilege.
Before shipping, ask three questions. Is the image small? Does data live outside the container? Does it run as a normal user? These checks prevent most production surprises.
- Bloated images → slim base, multi-stage builds, .dockerignore
- Data inside → volumes or a managed database
- Running as root → add a USER line
Key takeaways
- Apps break on servers because environments drift, not because code changes.
- Docker packages the app plus its environment into a portable container.
- A Dockerfile builds a layered image, so order your steps to make good use of the cache.
- Containers isolate processes while sharing the host kernel, which keeps them light.
- Registries share images, and Compose runs multi-container apps from one file.
- Keep images slim, store data in volumes, and never run as root.
Frequently asked questions
Is a Docker container the same as a virtual machine?
No. A virtual machine boots a full operating system of its own. A container shares the host's kernel, so it starts about as fast as a normal process and uses fewer resources.
What is the difference between a Docker image and a container?
An image is a read-only blueprint built from a Dockerfile. A container is a running, isolated copy of that image, and one image can start many containers.
Why is my Docker build slow every time I change code?
Usually the Dockerfile copies your code before installing dependencies, so every code change breaks the cache for the install step. Copy the package files and install dependencies first, then copy the rest of the code.
Do I lose data when a Docker container is removed?
Yes, if the data was stored inside the container. Keep persistent data in volumes or in a managed database so it survives when containers are replaced.
When should I use Docker Compose?
Use it when your app needs several services, such as a web app, a database and a cache. Compose defines them in one file and starts the whole stack with a single command.