Dockerfile Go Best Practice

Pendahuluan

Membuat container untuk aplikasi Go bukan hanya soal menjalankan docker build. Dalam production environment, tujuannya adalah menghasilkan Docker image yang sangat kecil, cepat di-build (dalam hitungan detik), dan sangat aman (lulus audit keamanan).

Ada empat pilar utama untuk Dockerfile Go yang efisien dan aman.

1. Multi-Stage Builds (Build Bertahap)

Ini adalah teknik paling fundamental. Kita tidak ingin mengirim Go SDK (yang berukuran >1GB) ke produksi

  • Tahap 1 (builder): Gunakan image lengkap seperti golang:1.25-alpine untuk meng-install dependensi dan mengkompilasi aplikasi Go Anda

  • Tahap 2 (final): Gunakan image kosong seperti scratch atau distroless. Kemudian, salin hanya hasil binary yang sudah dikompilasi dari tahap builder.

Hasil: Image final hanya berisi binary aplikasi Anda, mengurangi ukuran dari ~1GB menjadi di bawah beberapa MB.

2. Base Image Minimal (scratch vs distroless)

Menggunakan base image seperti ubuntu adalah kesalahan umum. Image besar ini mengandung ribuan paket dan library yang tidak Anda perlukan, yang masing-masing meningkatkan attack surface dan paparan CVE (kerentanan keamanan).

  • scratch: Image ini benar-benar kosong (0MB). Ini adalah yang paling minimal.

  • distroless: Image ini (seperti gcr.io/distroless/static-debian12) hanya berisi hal-hal esensial, seperti sertifikat CA dan data zona waktu, tetapi tidak memiliki shell atau package manager. Ini adalah pilihan yang sangat baik untuk keamanan.

3. Binary Statis (CGO_ENABLED=0)

Untuk menjalankan aplikasi Go di image scratch atau distroless, aplikasi tersebut harus berupa static binary (tidak memiliki dependensi eksternal seperti libc).

Anda dapat mencapainya dengan mengatur variabel lingkungan CGO_ENABLED=0 saat melakukan go build. Ini juga membantu mengurangi ukuran binary.

4. Jalankan sebagai Non-Root

Menjalankan container sebagai root adalah risiko keamanan besar. Jika terjadi container breakout, penyerang bisa mendapatkan akses root di host machine. Kepatuhan seperti SOC2 dan PCI-DSS secara eksplisit melarang eksekusi container sebagai root.

  • Cara Mudah: Gunakan base image distroless:nonroot yang sudah dikonfigurasi untuk berjalan sebagai non-root secara otomatis.

  • Cara Manual (jika pakai scratch): Buat user di tahap builder (RUN adduser -D appuser)lalu salin file /etc/passwd ke tahap final, dan gunakan USER appuser.


2. Mengoptimalkan Kecepatan Build (Layer Caching)

Kecepatan build 2 detik vs 2 menit bergantung pada seberapa baik Anda memanfaatkan Docker layer caching. Aturan utamanya adalah: urutan perintah COPY dan RUN sangat penting.

Strukturkan Dockerfile Anda dari lapisan yang paling jarang berubah ke yang paling sering berubah.

  1. Dependensi Sistem: Install ca-certificates atau tzdata (jarang berubah).

  2. Dependensi Go: Salin go.mod dan go.sum terlebih dahulu.

  3. Download Dependensi: Jalankan go mod download. Docker akan menyimpan lapisan ini.

  4. Salin Kode Sumber: Baru salin sisa kode sumber aplikasi Anda (COPY . .).

  5. Build: Jalankan go build.

Dengan cara ini, jika Anda hanya mengubah kode aplikasi (Langkah 4) tanpa mengubah dependensi (Langkah 2), Docker akan menggunakan kembali cache dari Langkah 3 (go mod download) dan tidak perlu mengunduh ulang semua library.


3. Optimasi Tambahan

Mengurangi Ukuran Binary

Gunakan ldflags saat membangun untuk menghapus simbol debug dan informasi DWARF, yang dapat mengurangi ukuran binary secara signifikan.

go build -ldflags="-w -s"

Build-Time Variables

Suntikkan informasi versi, commit, atau waktu build ke dalam binary Anda menggunakan ARG dan ldflags. Ini sangat berguna untuk monitoring dan debugging.

ARG VERSION=dev
ARG COMMIT=unknown
...
RUN go build -ldflags="-X main.Version=${VERSION} -X main.Commit=${COMMIT}" ...

4. Dockerfile Produksi Lengkap

Berikut adalah Dockerfile lengkap yang menggabungkan semua praktik terbaik: multi-stage, distroless:nonroot, caching dependensi, binary statis, dan build-time variables.

# --- TAHAP 1: BUILDER ---
# Gunakan image alpine yang ringan untuk build
FROM golang:1.25-alpine AS builder

# Argumen untuk build-time variables
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_TIME

# Install dependensi sistem: sertifikat (untuk HTTPS) dan timezone
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /build

# --- Caching Dependensi ---
# 1. Salin file mod dan sum
COPY go.mod go.sum ./
# 2. Download dependensi (akan di-cache)
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download

# 3. Salin sisa kode sumber
COPY . .

# 4. Build static binary
# - CGO_ENABLED=0 untuk static build
# - ldflags "-w -s" untuk mengurangi ukuran binary
# - ldflags "-X" untuk menyuntikkan build-time variables
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
    -ldflags="-w -s \
    -X main.version=${VERSION} \
    -X main.commit=${COMMIT} \
    -X main.buildTime=${BUILD_TIME}" \
    -o app ./cmd/server

# --- TAHAP 2: FINAL (PRODUKSI) ---
# Gunakan image distroless non-root: super minimal dan aman
FROM gcr.io/distroless/static-debian12:nonroot

# Salin data timezone dari builder
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# Salin binary aplikasi dari builder
COPY --from=builder /build/app /app

# Expose port
EXPOSE 8080

# Perintah untuk menjalankan aplikasi
ENTRYPOINT ["/app"]

Hasil Akhir

Dengan mengikuti struktur ini, Anda akan mendapatkan image Docker untuk aplikasi Go Anda yang:

  • Berukuran 8-12MB (dibandingkan 850MB+).

  • Build dalam 12 detik (setelah cache awal).

  • Sangat aman, berjalan sebagai non-root, dan memiliki attack surface minimal yang lolos audit SOC2 dan ISO27001.

Last updated