Docker cơ bản là kỹ năng nền cho backend dev hiện đại. Bài này đi từ "container là gì" đến viết Dockerfile chạy được Node.js + Postgres — kèm volume, network, multi-stage build.
Container là gì? Khác VM ra sao?
Virtual machine ảo hoá toàn bộ OS — mỗi VM có kernel riêng, init system riêng, ~1-2GB RAM. Container share kernel với host, chỉ ảo hoá process namespace + filesystem layer. Kết quả: container start trong giây, tốn ít RAM, mỗi server chạy được hàng trăm container.
Image vs Container
- Image: blueprint read-only. Layer cached, share giữa container.
- Container: instance chạy của image. Có ID, có state, có thể stop/start/remove.
- Registry: kho chứa image. Docker Hub mặc định, hoặc tự host (Harbor, GitHub Container Registry).
Dockerfile cho Node.js app
# Dockerfile — Node.js production
FROM node:22-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]
3 chi tiết quan trọng:
- Multi-stage:
depsstage build dependency, stage cuối chỉ copynode_modules— image production không có git, gcc... - USER node: chạy với non-root user. Nếu container bị compromise, attacker không có root.
- COPY package*.json trước: tận dụng layer cache. Khi anh sửa code (không sửa package.json), Docker dùng layer
npm ciđã cache — build nhanh.
.dockerignore — bắt buộc
node_modules
.git
.env
*.log
.next
.DS_Store
__tests__
*.md
Không có file này, COPY . . sẽ copy node_modules cũ vào image — chậm build, image phình to, có thể leak secret từ .env.
Build và run
# Build image
docker build -t alodev-api:latest .
# List image
docker images
# Run container
docker run -d --name api -p 3000:3000 \
-e DATABASE_URL=postgres://localhost/app \
alodev-api:latest
# Xem log
docker logs -f api
# Vào shell container
docker exec -it api sh
# Stop / remove
docker stop api && docker rm api
Volume — persist data
Container ephemeral — restart = mất state internal. Database data phải mount ra ngoài:
# Named volume (Docker quản lý)
docker run -d --name pg \
-e POSTGRES_PASSWORD=secret \
-v pg-data:/var/lib/postgresql/data \
postgres:16
# Bind mount (host directory)
docker run -d --name pg \
-e POSTGRES_PASSWORD=secret \
-v /host/pg:/var/lib/postgresql/data \
postgres:16
Named volume an toàn hơn bind mount — Docker quản lý, không lệ thuộc absolute path. Bind mount tốt cho dev (mount source code).
Network — container nói chuyện với nhau
# Tạo network
docker network create app-net
# Run database trong network
docker run -d --name pg --network app-net \
-e POSTGRES_PASSWORD=secret postgres:16
# Run app cùng network — connect "pg" hostname
docker run -d --name api --network app-net \
-e DATABASE_URL=postgres://postgres:secret@pg:5432/app \
-p 3000:3000 alodev-api:latest
Trong cùng network, container resolve nhau bằng tên (pg, api). Không cần biết IP. Đây là service discovery built-in.
Tối ưu image size
| Base image | Size | Khi dùng |
|---|---|---|
| node:22 | ~1GB | Dev, debug |
| node:22-slim | ~250MB | Production phổ biến |
| node:22-alpine | ~150MB | Pure JS, không native module |
| gcr.io/distroless/nodejs22 | ~120MB | Bảo mật cao, không shell |
Kết luận
Docker cơ bản gói gọn trong: image (read-only) → container (running) → volume (persist) → network (connect). Master 4 khái niệm này, anh deploy được app vào bất cứ server nào. Bước tiếp: học Dockerfile best practices và Docker Compose cho dev environment.