VM vs Container
Virtual machines emulate an entire OS, making them heavy and slow to start. Containers share the host kernel and package only the app plus its dependencies — launching in milliseconds and using a fraction of the resources.
| Virtual Machine | Container |
|---|---|
| Full OS per instance | Shared host kernel |
| GBs of disk space | MBs of disk space |
| Minutes to start | Milliseconds to start |
| Strong isolation (hypervisor) | Process-level isolation |
| Heavy resource usage | Lightweight and dense |
Core Concepts
Four primitives underpin everything in Docker. Understanding them removes the mystery from most Docker commands.
Dockerfile Anatomy
A Dockerfile is a sequence of instructions that Docker executes to produce an image. Multi-stage builds keep the final image lean by discarding build-time tooling.
# Stage 1: build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: runtime
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]Docker Compose
Compose defines your entire multi-container application in a single YAML file, making local development and CI reproducible with one command.
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://db/myapp
depends_on:
- db
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=secret
volumes:
pgdata:docker compose up -d starts all services in the background. docker compose logs -f app tails logs. docker compose down -v stops everything and removes volumes.