Step 1
Step 1 — Docker basics
35 min
Step 1 — Docker basics
"Works on my machine" stopped being a line thanks to Docker — package code + runtime + OS deps into one image, run anywhere.
1. Three words
- Image — read-only template (blueprint + parts)
- Container — running instance
- Volume — persistent storage outside containers
One image → many containers concurrently.
2. Docker vs VM
| VM | Container | |
|---|---|---|
| OS | full per VM | shares host kernel |
| Size | GBs | tens of MB |
| Start | minutes | seconds |
| Isolation | strong | process-level |
| Density | low | high (hundreds per server) |
3. Install
# macOS / Windows
# Docker Desktop (https://www.docker.com/products/docker-desktop/)
# Linux
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
docker --version
docker run hello-world
4. First containers
docker run -it --rm alpine sh
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:17
docker ps
docker ps -a
docker logs -f my-postgres
docker exec -it my-postgres psql -U postgres
docker stop my-postgres
docker rm my-postgres
5. Ten useful commands
docker images
docker pull node:20-alpine
docker rmi name
docker ps -a
docker exec -it name sh
docker logs -f name
docker inspect name
docker stats
docker network ls
docker system prune
6. Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
EXPOSE 3000
CMD ["pnpm", "start"]
Core keywords: FROM, WORKDIR, COPY, RUN, CMD, EXPOSE, ENV, ARG.
docker build -t my-app .
docker run -d -p 3000:3000 --name my-app-c my-app
curl http://localhost:3000
7. Multi-stage build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
Build tools stay in the builder stage; final image ~1/3 the size.
8. .dockerignore
node_modules
.next
.git
.env
.env.local
*.log
README.md
.github
.vscode
Faster builds, smaller images, safer.
9. Volumes
docker volume create pg-data
docker run -d --name pg \
-v pg-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:17
docker rm -f pg
docker run -d --name pg \
-v pg-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:17
# data preserved
docker run -v $(pwd)/data:/app/data my-app
10. Networks
docker network create my-net
docker run -d --name db --network my-net postgres:17
docker run -d --name app --network my-net \
-e DATABASE_URL=postgres://db:5432/... \
my-app
# app talks to "db" by container name
docker-compose automates this.
11. Image optimization tips
- Alpine bases (5–10MB)
- Distroless (no shell at all)
- Minimize layers:
RUN apt-get update && apt-get install -y ... - Robust
.dockerignore - COPY deps before source to preserve cache
12. Security basics
RUN addgroup -g 1001 app && adduser -D -u 1001 -G app app
USER app
- Never bake secrets into images
- Scan:
docker scout cves my-app - Consider
--read-only
13. Gotchas
- Port in use — pick another host port
- Slow builds — missing
.dockerignoreor bad COPY order - Container exits immediately — CMD not foreground (
docker logs) - Volume permissions — UID mismatch between host user and container user
- Disk full —
docker system prune -a
14. Try it
pnpm create next-app@latest my-test
cd my-test
# add the Dockerfile above
docker build -t my-test .
docker run -d -p 3000:3000 --name my-test-c my-test
curl http://localhost:3000
docker rm -f my-test-c
docker rmi my-test
Deeper
Next
Step 2 — run several containers together with docker-compose.