Docker & Docker Compose - Phần 2 - Image Layers, Build Cache, Dockerfile và những lỗi thường gặp
Ở Phần 1, mình đã nói về lý do Docker ra đời, khác biệt VM vs container, kiến trúc Docker, và Image vs Container. Phần 2 đi sâu hơn: image thực chất được cấu tạo từ nhiều layer, và hiểu cơ chế layer giúp bạn viết Dockerfile hiệu quả hơn hẳn — build nhanh hơn, image nhỏ hơn.
Docker Image được ghép từ nhiều layer
Mỗi dòng instruction trong Dockerfile (thường là) tạo ra một layer mới:

Bốn tính chất của layer: immutable (không đổi sau khi build), read-only (container chỉ thêm một lớp writable mỏng ở trên), cached (layer không đổi được tái sử dụng ở lần build sau), shared (nhiều image có thể cùng tham chiếu một layer). Hai tính chất ảnh hưởng trực tiếp tới công việc hàng ngày là cached (build lại nhanh hơn) và shared (tiết kiệm dung lượng) — immutable/read-only là lý do vì sao việc cache/share lại an toàn.
Vì sao layer sharing quan trọng

Docker chỉ lưu trữ và download layer Ubuntu/Node đúng một lần, dù bao nhiêu image tham chiếu tới chúng — chỉ layer App A/App B khác nhau mới lưu riêng. Đây là lý do một công ty có 50 microservice cùng build trên một base image không tốn 50 lần dung lượng hay băng thông tương ứng.
Build cache: viết sai thứ tự sẽ khiến build luôn chậm

Ở cách viết BAD, COPY . . copy toàn bộ source trong một bước — bất kỳ thay đổi nào trong source, kể cả sửa một ký tự comment, cũng làm layer này (và mọi layer sau) bị invalidate, buộc npm install và npm run build chạy lại từ đầu. Nếu bạn từng thấy build luôn cài lại toàn bộ node_modules dù không đổi dependency nào — đây chính là nguyên nhân.
Cách khắc phục (cách GOOD): copy package*.json trước, chạy npm install ngay sau, rồi mới copy phần còn lại của source. Vì package.json đổi ít hơn nhiều so với source code, layer npm install giờ chỉ invalidate khi dependency thực sự đổi.
Nguyên tắc tổng quát, áp dụng cho hầu như mọi Dockerfile chứ không riêng Node:
Bảng tham chiếu các instruction trong Dockerfile

Hai cặp dễ nhầm: ENV vs ARG — ENV tồn tại xuyên suốt vào container lúc chạy, ARG chỉ tồn tại lúc build rồi biến mất. CMD vs ENTRYPOINT — xem phần dưới.
Ví dụ thực tế, gộp tất cả instruction
dockerfile
FROM node:22 # base image
WORKDIR /app # working directory
COPY package*.json ./ # copy files
RUN npm install # runs during build
ENV NODE_ENV=production # env variable
ARG APP_VERSION=1.0.0 # build-time var
COPY . . # copy rest of source
EXPOSE 3000 # document port
ENTRYPOINT ["node"]
CMD ["server.js"] # startup command
Với ENTRYPOINT ["node"] + CMD ["server.js"]: ENTRYPOINT là phần cố định (executable), CMD chỉ là argument mặc định, hoàn toàn có thể bị ghi đè. Ví dụ docker run myimage other.js sẽ chạy node other.js — chỉ argument đổi, executable giữ nguyên.
Volumes: dữ liệu container là tạm thời
Container vốn ephemeral — xóa container đang chạy database, dữ liệu bên trong mất theo. Lỗi khá phổ biến với người mới, đặc biệt khi chạy docker compose down hoặc docker rm.
Giải pháp là Docker Volume — vùng lưu trữ nằm ngoài lớp writable của container, Docker quản lý và tồn tại độc lập với vòng đời container. Volume được thiết kế để sống sót qua việc xóa container — đó là mục đích của nó. Use case phổ biến: dữ liệu database, file upload, bất kỳ dữ liệu nào không thể mất khi container bị thay thế.
Networking: hiểu lầm phổ biến nhất
Mỗi container có network namespace riêng, dẫn tới hiểu lầm kinh điển:
- Bridge network — network cách ly mặc định cho container trên cùng host. Đi kèm Container DNS: Docker chạy DNS server nội bộ, tự động phân giải tên service (ví dụ
db) thành IP nội bộ hiện tại. - Host network — container dùng chung network stack với host, không có lớp DNS riêng.
Lưu ý: Bridge và Host là hai lựa chọn ngang hàng, còn Container DNS không phải lựa chọn thứ ba — nó là tính năng đi kèm riêng của Bridge. Đây cũng là lý do Bridge được dùng phổ biến nhất: tiện lợi gọi service bằng tên thay vì nhớ IP.
Ở Phần 3, mình sẽ chuyển sang Docker Compose — quản lý nhiều container cùng lúc, cấu trúc file docker-compose.yml, vòng đời up/down, và tổng hợp 5 best practice quan trọng nhất.
All rights reserved