Docker
Package an app and its dependencies into a portable image that runs as an isolated container on any host with a container runtime. “Works on my machine” → works everywhere.
Image vs Container
- Image — immutable, layered filesystem + metadata (built from a
Dockerfile). - Container — a running (or stopped) instance of an image, with a writable top layer.
- Registry — where images live (Docker Hub, Amazon ECR, GHCR).
Dockerfile Essentials
# --- build stage ---
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- runtime stage ---
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]- Layers are cached — order from least- to most-frequently-changing (deps before source) for fast rebuilds.
- Multi-stage builds keep build tools out of the final image → smaller, safer.
Everyday Commands
docker build -t myapp:1.0 .
docker run -p 3000:3000 --env-file .env myapp:1.0
docker ps # running containers
docker logs -f <id> # tail logs
docker exec -it <id> sh # shell inside
docker image prune -a # reclaim spaceCompose (multi-container, local)
services:
api:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16
environment: { POSTGRES_PASSWORD: dev }
volumes: ["pgdata:/var/lib/postgresql/data"]
volumes: { pgdata: {} }Best Practices
- Small base images (
-alpine,distroless); pin versions, notlatest. - Run as a non-root user; use
.dockerignore. - One concern per container; persist state in volumes, never the container layer.
- Scan images in CI (Trivy, ECR scanning).
Where they run
Locally with Compose; in prod via ECS & Fargate or Kubernetes.