Docker has a reputation for being intimidating, but the core idea is simple: it packages your application together with everything it needs to run, so it behaves the same everywhere. No more "works on my machine." This guide explains containers in plain language, walks through the handful of commands you actually use day to day, shows a real Dockerfile, and flags the beginner mistakes that trip people up early.
What changed in 2026
- Docker Desktop alternatives matured. Tools like Podman and OrbStack gave developers lighter, faster options, though Docker remains the default reference.
- Smaller base images became standard. Distroless and Alpine-based images are now common practice to shrink size and reduce attack surface.
- BuildKit is the default builder. Faster, cache-aware builds are on by default, so multi-stage builds are smoother than they used to be.
- AI assistants write Dockerfiles well. Coding assistants reliably scaffold a working Dockerfile, but you still need to understand it to debug and secure it.
Containers, images, and why they matter
A container is an isolated, lightweight package that holds your app and its dependencies - the runtime, libraries, and config. Because everything is bundled, the container runs identically on any machine with a container runtime. That solves the oldest problem in software: environment drift between laptops, CI, and production.
An image is the blueprint; a container is a running instance of that image. You build an image once and start as many containers from it as you like, the same way a class defines objects.
| Term |
What it is |
Analogy |
| Image |
The packaged, immutable blueprint |
A recipe |
| Container |
A running instance of an image |
A cooked dish |
| Dockerfile |
Instructions to build an image |
The written steps |
| Registry |
Where images are stored and shared |
A cookbook library |
The commands you actually use
docker build -t myapp . - build an image from the Dockerfile in the current directory and tag it myapp.
docker run -p 3000:3000 myapp - start a container and map port 3000 inside to 3000 on your machine.
docker ps - list running containers; add -a to see stopped ones too.
docker logs <id> - read a container's output when something goes wrong.
docker exec -it <id> sh - open a shell inside a running container to poke around.
Writing your first Dockerfile
A Dockerfile is a short list of instructions. Here is a typical one for a Node app, using a multi-stage build to keep the final image small:
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage - small and clean
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
For local development with multiple services, Docker Compose ties them together in one file:
services:
app:
build: .
ports: ["3000:3000"]
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: dev
Common beginner mistakes
- Putting secrets in the image. Anything you
COPY or ENV into an image is baked in and visible. Pass secrets at runtime instead.
- Giant images. Copying
node_modules and source without multi-stage builds produces bloated images. Use multi-stage and a small base.
- Ignoring
.dockerignore. Without it, you copy your entire .git folder and local junk into the build context, slowing builds.
- Running as root. Containers default to root; add a non-root user for anything exposed. This is a habit worth forming early, especially as you move into a real DevOps stack.
FAQ
Is Docker a virtual machine?
No. A VM runs a full guest operating system; a container shares the host kernel and isolates only the application. That makes containers far lighter and faster to start.
What is the difference between an image and a container?
An image is the immutable blueprint you build once. A container is a running instance of that image. You can run many containers from one image.
Do I need Docker Compose?
For local development with multiple services - app, database, cache - Compose is the easiest way to start them together with one command. For a single service, plain docker run is enough.
Should I run my database in Docker?
For local development, yes - it is convenient and disposable. In production as a beginner, prefer a managed database service and only containerize your application.
Where to go next
Build a pragmatic DevOps stack, understand Kubernetes next, and compare CI/CD tools.