0

Dockerfile Checklist - Checklist mình sử dụng khi viết Dockerfile

Nếu các bạn thường xuyên làm việc với Kubernetes hay CI/CD pipelines (như mình đã chia sẻ ở các series trước), thì chắc chắn Dockerfile là một thứ không thể thiếu. Viết Dockerfile thì dễ — ai cũng có thể làm được chỉ sau 5 phút Google — nhưng viết sao cho tối ưu, bảo mật và lightweight thì lại là một câu chuyện khác.

Hôm nay, mình sẽ tổng hợp lại một Dockerfile Creation Checklist mà mình thường dùng để review code cho anh em trong team. Bài viết này sẽ đi qua 3 khía cạnh chính: Configuration, Performance, và Security. Với mỗi ý, mình sẽ đưa ra ví dụ Good và Bad để mọi người dễ hình dung nhé.

Let's go! ⏩️

Lưu ý: Các ý mình list dưới đây là do mình tổng hợp từ nhiều nguồn, bạn hãy lựa chọn cái nào phù hợp có thể áp dụng cho dự án của mình nhé. Bạn không cần phải áp dụng toàn bộ các ý phía dưới vào Dockerfile của bạn.

Configuration

Các ý trong phần này tập trung vào việc làm cho image của bạn hoạt động ổn định, dễ cấu hình và tương thích tốt trên nhiều môi trường.

Support multiple machine architecture (amd64, arm64)

Đừng bao giờ hardcode platform nếu bạn muốn image chạy được trên cả Macbook M1/M2 (ARM) và server truyền thống (AMD).

  • Bad: Hardcode trong lúc build hoặc pull base image chỉ hỗ trợ 1 platform mà không check.
  • Good: Sử dụng biến TARGETARCH hoặc BUILDPLATFORM (của Docker Buildx) để tự động chọn binary phù hợp.
ARG TARGETARCH
COPY bin/app-${TARGETARCH} /app/main

Set explicit CMD / ENTRYPOINT / Run application process as PID 1

Luôn ưu tiên Exec form ["executable", "param1", "param2"] thay vì Shell form command param1. Shell form sẽ bọc command của bạn trong /bin/sh -c, khiến process của bạn không phải là PID 1 và không nhận được Unix signals (như SIGTERM để tắt graceful shutdown).

Container nên có một init process (PID 1) để xử lý việc thu dọn "zombie processes" và chuyển tiếp signals.

  • Bad: Chạy trực tiếp ứng dụng java/python mà không có init system xử lý signal.
  • Good: Sử dụng cờ --init khi chạy docker run hoặc dùng tini trong Dockerfile.
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"]

Add a .dockerignore file

Đừng gửi cả thư mục .git, node_modules hay các file log local vào Docker daemon context. Nó làm build chậm và tăng kích thước image vô ích.

  • Bad: COPY . . (hốt cả thế giới vào image).
  • Good: Có file .dockerignore:
.git
node_modules
build
*.log
.env

Define listen port (EXPOSE)

Đây là documentation cho người dùng biết container lắng nghe ở port nào.

  • Bad: Không khai báo gì cả, người chạy phải tự đoán.
  • Good:
EXPOSE 8080

Define HEALTHCHECK

Giúp Docker (và Kubernetes) biết ứng dụng của bạn còn "sống" hay không để restart.

  • Bad: Không có healthcheck (container chạy nhưng app bên trong treo cứng mà orchestrator không biết).
  • Good:

HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:8080/health || exit 1

Add labels (org.opencontainers.image.*)

Thêm metadata chuẩn OCI giúp quản lý image dễ hơn.

  • Bad: Image "vô danh", không biết ai maintain.
  • Good:

LABEL org.opencontainers.image.authors="Hoang Viet"
LABEL org.opencontainers.image.source="https://github.com/viet111/my-app"

Pin package + dependency versions

Tránh việc hôm nay build chạy, mai build lỗi do package tự update version mới.

  • Bad: apt-get install python3
  • Good: apt-get install python3=3.9.1-r0

Reproducible builds: use ARG

Linh hoạt thay đổi version mà không cần sửa cứng trong Dockerfile.

  • Bad: FROM node:14
  • Good:
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine

Performance

Tại phần này mình sẽ nêu ra các ý mà các bạn có thể áp dụng để có một image build nhanh, Image nhỏ, Cache ngon.

Use minimal, official base image

Dùng các bản phân phối nhỏ gọn như Alpine hoặc Distroless.

Use multi-stage builds

Đây là kỹ thuật quan trọng nhất để giảm size image cho các ngôn ngữ biên dịch (Go, Java, C++).

  • Bad: Build và Run trên cùng 1 stage. Image chứa cả source code, compiler (GCC, Maven...) và artifact.
  • Good: Stage 1 build, Stage 2 chỉ copy file binary sang một image rỗng (scratch/alpine).
# Build Stage
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .

# Run Stage
FROM alpine:latest
COPY --from=builder /app/main .
CMD ["./main"]

Prefer COPY over ADD

ADD có thể giải nén file tar và tải file từ URL (khó kiểm soát cache). COPY đơn giản và rõ ràng hơn.

  • Bad: ADD . /app
  • Good: COPY . /app (Chỉ dùng ADD khi bạn thực sự cần tải file remote hoặc giải nén tar).

Minimize number of layers

Kết hợp các lệnh RUN lại với nhau để giảm số lớp layer trung gian.

  • Bad:
RUN apt-get update
RUN apt-get install -y vim
RUN apt-get install -y curl
  • Good:

RUN apt-get update && apt-get install -y \
    vim \
    curl \
    && rm -rf /var/lib/apt/lists/*

Order layers for cache efficiency

Sắp xếp các câu lệnh trong Dockerfile từ ít thay đổi (OS, Dependencies) đến hay thay đổi (Source Code).

  • Bad: Copy source code trước khi cài dependencies. Mỗi lần sửa code, Docker phải cài lại thư viện từ đầu.
COPY . .
RUN npm install # Mất cache mỗi khi code đổi
  • Good:
COPY package.json .
RUN npm install # Được cache nếu package.json không đổi
COPY . . 

Security

Bảo mật container là một phần không thể thiếu trong quy trình DevSecOps.

Use base image with sha256 hash

Tags như :latest hay :18-alpine có thể bị thay đổi nội dung (mutable). Hash sha256 là bất biến. Việc định nghĩa mã SHA256 giúp ta tránh các cuộc tấn công supply chain.

  • Bad: FROM node:lts-alpine
  • Good: FROM node@sha256:52a6d123... (Đảm bảo chính xác 100% nội dung image).

Remove shell, package manager, debug tools

Kẻ tấn công xâm nhập được vào container cũng không làm gì được nếu không có shell (sh, bash) hay curl/wget.

  • Bad: Cài đủ thứ vim, curl, net-tools để tiện debug.
  • Good: Dùng Distroless images (không shell, không package manager).

Run static scans (hadolint, trivy)

Tích hợp vào CI/CD pipeline để chặn build nếu Dockerfile vi phạm quy tắc hay chứa các lỗ hổng CVE có mức ảnh hưởng HIGH, CRITICAL,...

Ví dụ: Chạy trivy image my-app:latest trước khi push lên registry.

Use non-root user

Mặc định Docker chạy với quyền root. Nếu hacker thoát khỏi container (container breakout), họ sẽ có quyền root trên máy host.

  • Bad: Không khai báo USER (mặc định là root).
  • Good:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

Don’t put any secrets, credentials in Dockerfile

Tuyệt đối không hardcode API Key, Password. Cho dù bạn có remove secret ở các step sau trong Dockerfile thì secret vẫn sẽ tồn tại ở layer trước đó.

  • Bad:
ENV DB_PASSWORD=secret123
COPY id_rsa /root/.ssh/
  • Good: Sử dụng Environment Variables lúc chạy (docker run -e) hoặc mount secret (--secret trong Docker Buildkit).

Hy vọng checklist này sẽ giúp anh em tự tin hơn khi viết Dockerfile. Lưu lại và áp dụng ngay cho dự án của mình nhé! Nếu thấy hay, đừng quên upvote và follow mình để đón chờ những bài viết tiếp theo về DevOps và Kubernetes.

Happy Coding! 👨‍💻

Nếu như bạn đang gặp khó khăn trong vấn đề chuyên môn, cần người hỗ trợ về mặt hệ thống, DevOps tools hay cần định hướng trong công việc thì mình tự tin có thể hỗ trợ được bạn. Liên hệ với mình để trao đổi thêm nhé https://hoangviet.io.vn/, mình rất vui khi được trao đổi và cộng tác với bạn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí