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 #

SetupUkuran Image
golang:latest + run800–900 MB
golang:alpine300–400 MB

Dengan Strategi yang Benar #

SetupUkuran Image
Multi‑stage + alpine20–40 MB
Distroless10–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=0 tidak 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 #

StrategiUkuranDebuggingSecurityRekomendasi
AlpineSedangMudahSedangDev / Ops
DistrolessKecilSulitTinggiProd
ScratchSangat kecilHampir nolTinggiKhusus

Kapan Strategi Mana Digunakan? #

KondisiPilihan
API productiondistroless
Service internalalpine
Agent / CLIscratch
Banyak CGOalpine

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.”

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact