- Cheat sheet 12 instructions Dockerfile đúng thứ tự (FROM → CMD), kèm lý do layer-caching, rules cache invalidation, và top 10 anti-patterns phổ biến với fix cụ thể.
- Dùng được ngay cho project Node/Go/Python.
TL;DR
Một Dockerfile tốt không chỉ chạy được — nó rebuild nhanh (nhờ layer cache) và chạy an toàn (nhờ non-root USER, exec-form ENTRYPOINT, digest pinning). Tất cả bắt đầu từ việc xếp 12 instructions đúng thứ tự, từ ít thay đổi nhất ở trên xuống thay đổi thường xuyên nhất ở dưới.
FROM → LABEL → ARG → ENV → WORKDIR → COPY/ADD → RUN → EXPOSE → VOLUME → USER → HEALTHCHECK → ENTRYPOINT/CMD- Mỗi lần 1 instruction bị thay đổi, Docker xoá cache của nó và toàn bộ các instructions phía sau. Đặt source code sai chỗ = mỗi lần sửa 1 dòng JS thì npm install chạy lại từ đầu.
- Exec-form
ENTRYPOINT ["bin", "flag"]bắt buộc — shell-form khiến app không nhận SIGTERM khidocker stop.
Thứ tự chuẩn 12 instructions
Đây là bảng cheat sheet gốc cộng với lý do vì sao mỗi instruction đứng đúng vị trí đó:
| # | Instruction | Vai trò | Vì sao ở đây |
|---|---|---|---|
| 1 | FROM | Base image | Bắt buộc dòng đầu — thiết lập filesystem gốc |
| 2 | LABEL | Metadata | Rất ít khi đổi → đặt cao để giữ cache |
| 3 | ARG | Build-time vars | Param hoá FROM tag hoặc RUN sớm; KHÔNG persist vào image |
| 4 | ENV | Runtime env vars | Được bake vào image; đổi value = cache miss NGAY tại dòng đó |
| 5 | WORKDIR | Thư mục làm việc | Dùng path tuyệt đối, thay thế RUN cd |
| 6 | COPY / ADD | Chép file/artifact | Cache key = nội dung file; luôn ưu tiên COPY |
| 7 | RUN | Cài package, build, cleanup | Mỗi RUN = 1 layer vĩnh viễn |
| 8 | EXPOSE | Document port | Chỉ mang tính tài liệu, không publish port |
| 9 | VOLUME | Persistent mount | Data ghi ở đây bypass union FS |
| 10 | USER | Chuyển sang non-root | Sau khi cài/copy, trước ENTRYPOINT |
| 11 | HEALTHCHECK | Probe sức khoẻ | Chỉ instruction cuối cùng có hiệu lực |
| 12 | ENTRYPOINT / CMD | Lệnh chạy khi start | Dùng exec form JSON array |
Vì sao thứ tự quan trọng — layer cache
Docker build image theo layer, mỗi instruction tạo 1 layer. Khi rebuild, Docker duyệt từ trên xuống và kiểm tra từng layer còn dùng lại được không. Cache miss đầu tiên invalidate toàn bộ layers phía dưới.
Đây là pattern quen thuộc nhất làm chậm dev loop:
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]Mỗi lần bạn sửa 1 ký tự trong server.js, COPY . . cache bust → npm install chạy lại từ đầu. Ở project thật, đây là 30–120 giây lãng phí mỗi lần rebuild.
Fix:
FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]npm ci được cache chừng nào lockfile chưa đổi. Rebuild rớt xuống còn vài giây.
Cache rules chi tiết
- RUN cache key = literal string của lệnh.
RUN apt-get dist-upgrade -yKHÔNG tự invalidate theo thời gian — chỉ khi bạn sửa dòng đó hoặc dùng--no-cache. - COPY/ADD cache key = nội dung file + permissions.
mtimebị bỏ qua. Đổi 1 byte trong file được copy = bust cache layer đó và tất cả layers phía sau. - ARG cache miss xảy ra ở lần dùng đầu tiên, không phải ở dòng khai báo.
ARG VERSIONmột mình là cache-safe;RUN echo $VERSIONmới bust cache khi VERSION đổi. - ENV cache miss xảy ra NGAY tại dòng ENV khi value đổi — vì ENV được bake vĩnh viễn vào layer đó.
Một nguyên nhân cache-miss âm thầm rất khó debug: RUN apt-get update tách riêng khỏi RUN apt-get install. Docker thấy string update không đổi → reuse cached layer → bạn install phiên bản package cũ 6 tháng trước. Luôn chain 2 lệnh trong cùng 1 RUN:
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*Top 10 anti-patterns & fix
| Anti-pattern | Hậu quả | Fix |
|---|---|---|
Tách apt-get update / install | Cài package cũ do cache hit | Gộp 1 RUN + --no-install-recommends + cleanup |
COPY . . trước cài deps | Mỗi code change rebuild deps từ đầu | Copy manifest trước, cài, rồi copy code |
FROM ubuntu:latest | Build gãy bất ngờ trong tương lai | Pin major.minor hoặc SHA digest |
| Chạy bằng root | Privilege escalation khi container breakout | USER 10000:10001 (UID ≥ 10000) |
Không có .dockerignore | Leak .env, .git vào image | Ignore secrets, node_modules, logs |
| Mỗi command 1 RUN | Layer phình, file "đã xoá" còn trong history | Chain bằng && + \ |
| Secret qua ARG / ENV | Hiện rõ trong docker history | RUN --mount=type=secret,id=... |
| Không có HEALTHCHECK | App hang im lặng, orchestrator không biết | Thêm HEALTHCHECK (hoặc K8s livenessProbe) |
Dùng ADD khi COPY đủ | Tar auto-extract = Zip Slip / path traversal; URL = MITM | Dùng COPY, chỉ ADD khi cần extract tar |
| Shell-form ENTRYPOINT | Không phải PID 1, không nhận SIGTERM | Exec form JSON array |
Template mẫu cho Node app
Kết hợp đầy đủ thứ tự chuẩn + multi-stage + non-root + HEALTHCHECK:
# syntax=docker/dockerfile:1.7
FROM node:20-alpine@sha256:... AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
FROM node:20-alpine@sha256:... AS runtime
LABEL org.opencontainers.image.source="https://github.com/you/app"
ENV NODE_ENV=production PORT=3000
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=10000:10001 . .
EXPOSE 3000
USER 10000:10001
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
ENTRYPOINT ["node"]
CMD ["server.js"]Chú ý: --mount=type=cache giữ npm cache dir giữa các build (BuildKit), không bake vào image. COPY --chown đặt ownership ngay lúc copy, tránh phải thêm RUN chown.
Những gì BuildKit mới (2026) đáng thêm vào flow
Syntax Dockerfile core đã ổn định nhiều năm; thứ tự 12 instructions không đổi. Điểm nâng cấp đáng học:
RUN --mount=type=secret,id=npmrc— inject token lúc build mà không để lại trong image history.RUN --mount=type=cache,target=/root/.npm— cache persistent giữa các build host, dùng cho npm / pip / apt / go mod cache.COPY --link— copy không invalidate layers phía sau khi có thể, giúp cache hit nhiều hơn.- Heredocs trong RUN:
RUN <<EOF ... EOF— script nhiều dòng không cần&&/\.
Tất cả đều là opt-in và dùng được khi khai báo # syntax=docker/dockerfile:1.7 ở dòng đầu Dockerfile.
Nguồn: Docker Docs — Best practices, Dockerfile reference, Sysdig — Top 21 Dockerfile best practices, Docker Blog — Intro Guide, @devops_nk tweet gốc.


