Dockerfile best practices chuẩn 2026 quyết định phần lớn chất lượng image khi chạy thật. Bài này đi qua 10 quy tắc + Dockerfile mẫu cho Node.js, Python, Go ở cuối.

1. Luôn dùng multi-stage build
Tách build dependencies khỏi runtime. Image final chỉ chứa app + runtime deps:
dockerfileFROM node:22-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
USER node
CMD ["node", "dist/server.js"]Stage builder có TypeScript, dev deps, source. Stage cuối chỉ có dist + production deps. Image cuối ~200MB thay vì 800MB.
2. Pin version base image
dockerfile# ❌ Sai - không reproducible
FROM node:latest
FROM node:22
# ✓ Đúng - cụ thể
FROM node:22.5.1-slim
# Tốt hơn - pin digest
FROM node:22.5.1-slim@sha256:abc123...Renovate/Dependabot tự bump version có PR - anh review từng update.
3. Tận dụng layer cache
Đặt instruction ít thay đổi trước:
dockerfile# ❌ Sai - sửa code = re-run npm ci
COPY . .
RUN npm ci
# ✓ Đúng - sửa code không invalidate npm ci
COPY package*.json ./
RUN npm ci
COPY . .4..dockerignore đầy đủ
textnode_modules
npm-debug.log
.git
.gitignore
.env*
.next
.cache
*.md
.vscode
.idea
__tests__
coverage
.DS_StoreKhông chỉ vì size -.env không được vào image. Secret leak vào registry là incident phổ biến.
5. Chạy non-root user
dockerfileFROM node:22-slim
RUN groupadd -r app && useradd -r -g app -u 1001 app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["node", "server.js"]Image official Node có user node sẵn (UID 1000). Dùng USER node là đủ.
6. Handle signal đúng - dùng exec form
dockerfile# ❌ Sai - shell form, không nhận SIGTERM
CMD node server.js
# ✓ Đúng - exec form, app trực tiếp PID 1
CMD ["node", "server.js"]Khi shell form, node là child của shell - SIGTERM gửi shell, không tới app. App không graceful shutdown được.
7. Combine RUN để giảm layer
dockerfile# ❌ Tạo 3 layer
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# ✓ 1 layer + dọn cache trong cùng layer
RUN apt-get update \
&& apt-get install -y curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*8. HEALTHCHECK cho orchestrator
dockerfileHEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/healthz || exit 1K8s/ECS dùng để restart container khi unhealthy. Endpoint /healthz nên kiểm DB connection, không chỉ "200 OK".
9. KHÔNG bao giờ ARG/ENV cho secret
dockerfile# ❌ Secret leak vào image layer
ARG DATABASE_PASSWORD
ENV DB_PASS=$DATABASE_PASSWORD
# ✓ Dùng BuildKit secret mount
# syntax=docker/dockerfile:1.4
RUN --mount=type=secret,id=db_pass \
cat /run/secrets/db_pass | npm run migrateBuild với:
bashDOCKER_BUILDKIT=1 docker build --secret id=db_pass,src=./db_pass.txt .10. Scan image trước khi push
bash# Trivy
trivy image alodev-api:latest
# Snyk
snyk container test alodev-api:latest
# Docker Scout (built-in)
docker scout cves alodev-api:latestTích hợp CI: fail build nếu có CRITICAL vuln chưa patch.
Full example: Node.js production
dockerfile# syntax=docker/dockerfile:1.6
FROM node:22.5.1-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
FROM node:22.5.1-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs22-debian12
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
EXPOSE 3000
USER nonroot
CMD ["dist/server.js"]Distroless: không có shell, không apt, gần như không attack surface. Image ~120MB.
Kết luận
10 quy tắc Dockerfile best practices: multi-stage, pin version, layer cache, non-root, exec form, combine RUN, healthcheck, secret mount, scan. Áp dụng đầy đủ, image anh sẽ nhỏ hơn 3x và an toàn hơn nhiều. Đọc thêm Docker cơ bản nếu mới bắt đầu, và CI/CD GitHub Actions để tự động build + scan.





