Golang #
Jika Java berjuang dengan fat JAR, Node.js dengan node_modules, dan Python dengan native dependency, maka Golang sering disebut “paling container‑friendly”. Klaim ini tidak sepenuhnya salah—namun juga sering disalahpahami.
Banyak image Golang di production tetap berukuran 200–400 MB, padahal Go mampu menghasilkan single static binary. Artikel ini membahas secara mendetail, praktis, dan berorientasi production tentang bagaimana membangun Docker image Golang yang kecil, aman, dan operasional‑friendly, tanpa jatuh ke jebakan “optimasi buta”.
Realita Ukuran Docker Image Golang #
Tanpa Strategi #
| Setup | Ukuran Image |
|---|---|
| golang:latest + run | 800–900 MB |
| golang:alpine | 300–400 MB |
Dengan Strategi yang Benar #
| Setup | Ukuran Image |
|---|---|
| Multi‑stage + alpine | 20–40 MB |
| Distroless | 10–25 MB |
| Scratch (static) | 5–15 MB ⭐ |
Jika image Golang kamu >100 MB, hampir pasti kamu membawa tool build ke runtime atau binary‑nya tidak dioptimasi.
Kenapa Golang Bisa Sangat Kecil? #
1. Single Binary #
Golang menghasilkan satu executable yang:
- Tidak butuh runtime eksternal
- Tidak butuh interpreter
2. Static Linking #
Dengan konfigurasi yang tepat:
- Semua dependency dikompilasi ke binary
- Tidak perlu libc
3. Tidak Ada Dependency Tree Runtime #
Tidak ada node_modules, tidak ada site-packages.
Prinsip Utama Image Golang Production #
Runtime image hanya berisi binary + hal yang benar‑benar dibutuhkan.
Idealnya:
- Tidak ada compiler
- Tidak ada shell (opsional)
- Tidak ada package manager
Strategi 1: Multi‑stage Build (Fondasi Wajib) #
Dockerfile Dasar #
# =====================
# Build stage
# =====================
FROM golang:1.22-alpine AS builder
WORKDIR /app
RUN apk add --no-cache git
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o app
# =====================
# Runtime stage
# =====================
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]
Ukuran tipikal: 20–40 MB.
Strategi 2: Distroless (Production Default) #
Jika kamu ingin:
- attack surface minimal
- konsistensi production
Dockerfile Distroless #
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux \
go build -ldflags="-s -w" -o app
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 8080
USER nonroot:nonroot
CMD ["./app"]
Ukuran: 10–25 MB.
Strategi 3: Scratch (Ekstrem tapi Murni) #
Dockerfile Scratch #
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux \
go build -ldflags="-s -w" -o app
FROM scratch
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["/app/app"]
Ukuran: 5–15 MB.
Catatan Penting Scratch #
- ❌ Tidak ada CA cert
- ❌ Tidak ada timezone
- ❌ Debugging nol
Biasanya tidak direkomendasikan kecuali kamu tahu betul konsekuensinya.
CGO: Musuh Diam‑diam Image Kecil #
Jika aplikasi kamu:
- menggunakan SQLite
- menggunakan image processing
- menggunakan OS‑level library
Maka:
CGO_ENABLED=0tidak bisa- Binary akan bergantung pada libc
Solusi:
- alpine + musl
- atau distroless
cc
Optimasi Binary (Sering Terlupakan) #
Gunakan -ldflags
#
go build -ldflags="-s -w"
Menghapus:
- debug symbol
- DWARF info
Gunakan upx (Opsional)
#
- Binary bisa 30–50% lebih kecil
- Trade‑off: startup time
Security & Production Readiness #
Non‑root User #
Selalu jalankan sebagai non‑root:
USER nonroot:nonroot
Healthcheck #
Gunakan HTTP healthcheck, bukan shell.
Logging #
- STDOUT / STDERR
- Structured log
Perbandingan Strategi #
| Strategi | Ukuran | Debugging | Security | Rekomendasi |
|---|---|---|---|---|
| Alpine | Sedang | Mudah | Sedang | Dev / Ops |
| Distroless | Kecil | Sulit | Tinggi | Prod |
| Scratch | Sangat kecil | Hampir nol | Tinggi | Khusus |
Kapan Strategi Mana Digunakan? #
| Kondisi | Pilihan |
|---|---|
| API production | distroless |
| Service internal | alpine |
| Agent / CLI | scratch |
| Banyak CGO | alpine |
Penutup #
Golang memberi kemewahan yang jarang dimiliki bahasa lain: kontrol penuh atas runtime. Namun kemewahan ini sering disia‑siakan dengan image yang gemuk dan tidak disiplin.
Image kecil bukan tujuan akhir—ia adalah efek samping dari arsitektur build yang bersih.
Jika Docker image Golang kamu besar, hampir pasti masalahnya bukan di Go, tapi di kebiasaan build dan boundary antara build vs runtime.
“Go tidak menjanjikan image kecil. Go memberi kesempatan—engineer‑lah yang menentukan apakah kesempatan itu dipakai.”