Bài 1: Dockerfile chuẩn cho Go (Multi-stage Build & Dev vs Prod)
Khi bắt đầu đưa một ứng dụng Golang vào Docker, sai lầm phổ biến nhất của các lập trình viên mới là sử dụng một Dockerfile cồng kềnh cho mọi môi trường. Kết quả là dung lượng ảnh (image size) phình to lên tới gần 1GB, thời gian build lâu, và quan trọng nhất là không hỗ trợ trải nghiệm code mượt mà (thiếu tính năng live-reload).
Trong bài học này, chúng ta sẽ phân tách rõ ràng hai chiến lược Dockerfile hoàn toàn khác nhau: một dành cho Môi trường Phát triển (Development) và một dành cho Môi trường Triển khai (Production) thông qua kỹ thuật Multi-stage Build.
1. Phân biệt Development vs. Production Dockerfile
Mục tiêu của hai môi trường này trái ngược nhau hoàn toàn:
| Tiêu chí | Môi trường Development (Dev) | Môi trường Production (Prod) |
|---|---|---|
| Mục tiêu tối thượng | Tốc độ phản hồi nhanh, hỗ trợ sửa code trực tiếp (Hot-reload). | Dung lượng tối thiểu, bảo mật tối đa, hiệu năng cao. |
| Mã nguồn | Mount source code từ máy thật vào container qua Volume. | Biên dịch sẵn thành tệp nhị phân (Binary) đóng gói bên trong image. |
| Công cụ đi kèm | Cần compiler (go), các tool dev như air, git, v.v. |
Hoàn toàn sạch bóng công cụ, chỉ chứa file binary chạy độc lập (thậm chí chạy trên scratch). |
2. Chiến lược 1: Dockerfile cho Development (Tối ưu cho Local)
Ở môi trường Dev, bạn không muốn mỗi lần sửa một dòng code lại phải gõ lệnh docker build mất vài phút. Chúng ta cần container giữ lại môi trường Go đầy đủ và sử dụng Volume để đồng bộ code thời gian thực.
Tạo file Dockerfile.dev:
# Sử dụng image chính thức của Go phiên bản mới nhất
FROM golang:1.22-alpine
# Cài đặt các công cụ cơ bản phục vụ dev (git, build-base nếu cần cgo)
RUN apk add --no-cache git
# Thiết lập thư mục làm việc bên trong container
WORKDIR /app
# Sao chép file khai báo dependencies trước để tận dụng Docker Layer Caching
COPY go.mod go.sum ./
RUN go mod download
# Toàn bộ mã nguồn thực tế sẽ được đồng bộ từ máy thật thông qua Docker Compose Volume,
# nên ở Dockerfile dev chúng ta không nhất thiết phải COPY toàn bộ source ngay từ đầu.
# Cổng mặc định của ứng dụng Go
EXPOSE 8080
# Lệnh mặc định khi container khởi động (có thể override bằng docker-compose)
CMD ["go", "run", "main.go"]
3. Chiến lược 2: Dockerfile cho Production với Multi-stage Build
Khi đưa lên Production, bạn không cần trình biên dịch Go nặng nề nằm trong image nữa. Kỹ thuật Multi-stage Build cho phép chúng ta chia Dockerfile thành nhiều giai đoạn (stages):
- Stage 1 (Builder): Dùng image Go nặng để tải thư viện và biên dịch mã nguồn thành một file binary duy nhất.
- Stage 2 (Runner): Lấy file binary từ Stage 1 ném sang một image siêu nhẹ (như
alpinehoặcscratch) để chạy. Image cuối cùng sẽ sạch sẽ và chỉ nặng vài chục Megabyte.
Tạo file Dockerfile:
# ==========================================
# STAGE 1: Build file binary
# ==========================================
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Cài đặt git phục vụ việc fetch các private module nếu có
RUN apk add --no-cache git
# Tải dependencies
COPY go.mod go.sum ./
RUN go mod download
# Sao chép toàn bộ mã nguồn vào container
COPY . .
# Biên dịch ứng dụng thành file binary tĩnh hoàn toàn (CGO_ENABLED=0)
# -ldflags="-s -w" giúp loại bỏ symbol table và debug information để nén tối đa kích thước file
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main .
# ==========================================
# STAGE 2: Runtime Environment tối giản
# ==========================================
FROM alpine:3.20
WORKDIR /app
# Cài đặt chứng chỉ SSL cơ bản (thường cần thiết nếu ứng dụng Go gọi HTTPS ra ngoài)
RUN apk add --no-cache ca-certificates tzdata
# SAO CHÉP QUAN TRỌNG: Chỉ lấy đúng file binary từ Stage 1 sang đây
COPY /app/main .
# (Tùy chọn) Sao chép thêm file cấu hình .env hoặc template nếu app yêu cầu
# COPY --from=builder /app/config ./config
EXPOSE 8080
# Chạy trực tiếp file binary
CMD ["./main"]
Thành quả:
Khi bạn build image production này, dung lượng của nó chỉ rơi vào khoảng 15MB - 25MB (thay vì hơn 800MB nếu dùng nguyên image golang thuần), giúp việc kéo (pull) và đẩy (push) image lên Docker Hub / AWS ECR diễn ra trong chớp mắt, đồng thời giảm thiểu bề mặt tấn công bảo mật (security vulnerability).
💡 Tổng Kết
Hiểu và tách biệt rõ hai Dockerfile cho Dev và Prod là nền tảng đầu tiên của một quy trình CI/CD chuẩn chỉnh.
Tuy nhiên, với cấu hình Dockerfile.dev ở trên, mỗi lần bạn sửa code, container vẫn phải chạy lại lệnh go run thủ công hoặc restart. Làm thế nào để đạt được trải nghiệm Hot-reload tự động biên dịch lại ngay khi bấm Ctrl+S giống như Node.js hay React?
All rights reserved