Docker for Beginners: From Zero to Your First Container
Docker shows up in nearly every modern DevOps job description, but a lot of beginners learn the commands without ever understanding why containers exist. This guide fixes that: by the end, you'll understand the problem Docker solves, and you'll have built and run your first container.
The problem Docker solves
Before containers, "it works on my machine" was a running joke with real consequences. An app might depend on a specific Node.js version, a particular set of system libraries, and environment variables that only exist on the developer's laptop. Moving that app to a teammate's machine, a test server, or production meant hoping the environments matched closely enough.
Docker packages an application together with everything it needs to run, including code, runtime, system tools, libraries, and configuration, into a single unit called an image. Run that image anywhere Docker is installed and you get identical behavior, because you're not relying on the host machine's setup at all.
Containers vs. virtual machines
A common point of confusion: containers are not tiny virtual machines. A VM virtualizes an entire operating system, including its own kernel, which is heavy, often gigabytes in size and slow to boot. A container instead shares the host machine's kernel and isolates only the application layer using Linux namespaces and cgroups, which makes containers dramatically lighter (often megabytes) and faster to start (often under a second).
The practical result: you can run dozens of containers on the same hardware that would comfortably fit only a handful of VMs.
Key concepts
- Image: a read-only template containing your application and its dependencies. Think of it as a snapshot.
- Container: a running instance of an image. You can start, stop, and delete containers without touching the underlying image.
- Dockerfile: a text file with instructions for building an image, step by step.
- Registry: a place to store and share images, like Docker Hub.
- Volume: a mechanism for persisting data outside a container's lifecycle, since containers are ephemeral by design.
Writing your first Dockerfile
Here's a minimal Dockerfile for a Node.js app:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Each line adds a layer to the image:
FROMsets the base image, here a lightweight Node.js runtime.WORKDIRsets the working directory inside the container.COPYandRUNinstall dependencies first (so Docker can cache this layer and skip reinstalling packages when only your source code changes).EXPOSEdocuments which port the app listens on.CMDdefines the command that runs when the container starts.
Building and running
# Build an image from the Dockerfile in the current directory
docker build -t my-app:latest .
# Run a container from that image, mapping port 3000
docker run -p 3000:3000 my-app:latest
# List running containers
docker ps
# Stop a container
docker stop <container_id>
That's it. Your app is now running inside an isolated, reproducible environment.
CMD vs. ENTRYPOINT
A question that trips up a lot of beginners, and one of the most common Docker interview questions: what's the difference between CMD and ENTRYPOINT?
ENTRYPOINTdefines the fixed executable that always runs.CMDsupplies default arguments, which can be overridden when you run the container.
If you want a container that always runs a specific binary but lets users override its arguments, combine both:
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
Running docker run my-image --port 9000 overrides the CMD arguments while keeping the ENTRYPOINT fixed.
Persisting data with volumes
Containers are meant to be disposable. Delete one and its filesystem changes vanish. For anything that needs to survive restarts (a database's data directory, for example), mount a volume:
docker run -v my-data:/var/lib/postgresql/data postgres:16
Docker manages the my-data volume independently of the container, so you can recreate the container without losing data.
Multi-container apps with Docker Compose
Real applications usually need more than one container: an API, a database, a cache. docker-compose.yml lets you define and run them together:
services:
api:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
Then a single docker compose up starts your whole stack.
Where to go next
Once the basics click, the natural next step is learning how containers get orchestrated at scale, which is exactly what Kubernetes is for. Try the Docker quiz on OpsQuiz to test what you've just learned, and check out our Kubernetes for beginners guide next.