Web Artisan
Chapter 05 · Docker

Registry & Image Produksi
yang Aman

Stack jalan mulus di laptop; kini kita kirim ke produksi. Beri tag yang bisa ditelusuri sampai ke commit, distribusikan lewat registry, lalu keraskan image agar kecil, non-root, dipindai, dan tanpa satu pun secret di dalam layer.

Tag, digest, registryImage dikeraskan~21 menit baca

Di Chapter 4 stack skincare berjalan rapi di laptop, tapi image-nya masih lokal. Chapter ini adalah busur “mengirim ke produksi” dalam dua langkah yang berpasangan: pertama distribusi yang reproducible (tag yang terlacak plus registry), lalu pengerasan image produksi (kecil, non-root, dipindai, migrasi terkontrol). Keduanya berbagi satu prinsip yang sudah muncul di Chapter 1: tag itu mutable, hanya digest yang menjamin byte yang sama.

01

Image Tagging, Versioning, dan Registry

Tag yang jelas supaya deploy terlacak dan rollback mudah

Image yang sudah dibangun tidak ada gunanya kalau tidak bisa kamu temukan lagi dengan pasti versi mana yang sedang berjalan di production.

Tag adalah alamat manusiawi untuk sebuah image. Tanpa disiplin penamaan, kamu akan terjebak pertanyaan klasik saat insiden: “yang lagi jalan di production itu build yang mana?” Jawaban yang baik bukan “yang terbaru”, tapi sebuah identitas yang bisa ditelusuri sampai ke commit Git tertentu. Registry adalah tempat image itu disimpan dan dibagikan, persis seperti registry npm menyimpan paket, hanya saja yang kita simpan adalah artefak runtime yang sudah jadi, bukan sumber.

🌉Jembatan: dari versi paket npm ke versi artefak image

Di npm kamu kunci versi lewat package-lock.json agar instalasi deterministik; di Docker, tag semver plus digest adalah penguncinya, supaya “yang dideploy” selalu artefak yang sama persis.

Jebakan tag latest

latest hanyalah label biasa yang menunjuk ke image terakhir yang kamu tag dengan nama itu. Ia tidak berarti “versi paling baru” secara semantik dan tidak deterministik: dua server yang menjalankan docker pull skincare-api:latest pada waktu berbeda bisa mendapat biner yang berbeda. Saat terjadi bug, kamu kehilangan kemampuan rollback karena tidak tahu versi sebelumnya bernama apa.

⚠️Jangan andalkan latest untuk deploy serius

Pin tag semver atau git SHA, dan untuk jaminan penuh referensikan digest image@sha256:…; latest mutable dan membuat deploy tidak reproducible.

Tiga lapis penamaan

JenisContohSifat
Semantic tagskincare-api:1.4.0Dibaca manusia, mengikuti rilis
Git SHA tagskincare-api:9f3a1c2Telusur balik ke commit persis
Immutable digestskincare-api@sha256:ab12…Tidak bisa berubah, jaminan byte-identik

Praktik yang sehat: satu image fisik diberi beberapa tag sekaligus saat build, sehingga satu artefak bisa dirujuk lewat versi semver yang ramah manusia maupun SHA yang presisi.

Terminal
# satu build, beberapa tag menunjuk image fisik yang sama docker build \ -t skincare-api:dev \ -t skincare-api:$(git rev-parse --short HEAD) \ . # beri ulang tag image yang sudah ada untuk tujuan registry docker tag skincare-api:$(git rev-parse --short HEAD) \ ghcr.io/owner/skincare-api:$(git rev-parse --short HEAD)
💡Rollback nyata bersandar pada digest

Saat insiden produksi, “balikkan ke versi kemarin” hanya bisa dilakukan kalau kamu tahu identitas pastinya. Catat digest @sha256: tiap rilis (mis. di catatan deploy); rollback berarti deploy ulang digest lama yang dijamin byte-identik, bukan menebak-nebak tag mana yang dulu dipakai.

Registry: tempat image tinggal

Ada beberapa registry umum, semuanya bicara protokol yang sama sehingga alur login, build, tag, push, pull identik; yang berbeda hanya nama host dan cara autentikasinya.

RegistryHostCocok untuk
Docker Hubdocker.ioImage publik, base image resmi
GitHub Container Registryghcr.ioImage privat menyatu dengan repo & CI
AWS ECR<acct>.dkr.ecr.<region>.amazonaws.comDeploy di ekosistem AWS
Terminal
# login ke ghcr.io (token via stdin, jangan tempel di argumen) echo "$GHCR_TOKEN" | docker login ghcr.io -u owner --password-stdin # push tag SHA yang sudah diberi prefix host registry docker push ghcr.io/owner/skincare-api:$(git rev-parse --short HEAD) # di sisi lain (CI/staging/prod) tarik versi yang sama persis docker pull ghcr.io/owner/skincare-api:9f3a1c2
💡Login ECR berumur pendek

ECR memberi password sementara, jadi alur login-nya: aws ecr get-login-password —region eu-west-1 | docker login —username AWS —password-stdin <acct>.dkr.ecr.eu-west-1.amazonaws.com, lalu push seperti biasa.

Build once, run anywhere

Inti dari registry adalah memisahkan kapan image dibangun dari kapan ia dijalankan. Image dibangun sekali di CI, lalu artefak yang sama persis ditarik oleh staging dan production. Tidak ada lagi “build ulang di server” yang berisiko menghasilkan biner berbeda karena perbedaan lingkungan.

flowchart LR
  CI["CI: docker build + push"] --> REG["Registry<br/>ghcr.io/owner/skincare-api:9f3a1c2"]
  REG --> STG["Staging<br/>docker pull :9f3a1c2"]
  REG --> PRD["Production<br/>docker pull :9f3a1c2"]

Satu artefak, banyak target. Staging dan production menarik tag SHA yang sama, sehingga apa yang diuji adalah apa yang dirilis.

Image sudah bisa dibagikan dengan identitas yang jelas. Tapi sebelum di-push ke produksi, image itu harus dikeraskan dulu.

02

Security Dockerfile dan Image Production

Non-root, base minimal, dev vs prod, migration terkontrol

Image production yang baik membawa sesedikit mungkin: satu biner, tanpa shell, tanpa secret, dan berjalan sebagai user biasa.

Setiap hal yang ada di dalam image adalah permukaan serang. Shell, package manager, compiler, dan tool debug semuanya berguna saat mengembangkan, tapi di production mereka hanya menambah cara bagi penyerang untuk bergerak setelah masuk. Prinsipnya sederhana: image production harus kecil, berjalan non-root, dan tidak menyimpan kredensial di dalam layer. Banyak dari ini sudah kita capai di Chapter 2 lewat multi-stage dan distroless; di sini kita rapikan dan tegaskan jadi disiplin produksi.

🌉Jembatan: dari audit dependensi ke pemindaian image

Sama seperti npm audit memeriksa kerentanan di pohon dependensi JS, docker scout cves atau trivy image memindai kerentanan di image kamu, termasuk paket OS pada base image, bukan cuma kode aplikasi.

Dockerfile yang diperketat

Tiga pengetatan penting: pin versi base image (jangan biarkan mengambang), pakai base runtime minimal, dan jalankan sebagai user non-root. Distroless cocok untuk biner Go statis karena hampir kosong, tidak punya shell sama sekali.

Dockerfile
# --- build --- FROM golang:1.26 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app ./cmd/server # --- runtime --- FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /app /app USER nonroot:nonroot EXPOSE 8080 ENTRYPOINT ["/app"]
⚠️Jangan jalankan proses sebagai root

Default container berjalan sebagai root; bila penyerang menembus aplikasi, root di dalam container memperbesar dampaknya. Selalu set USER non-root, lewat tag :nonroot distroless atau adduser pada alpine.

Dua image untuk dua dunia

Kesalahan umum adalah mengirim image development ke production. Image dev sengaja gemuk: ada hot reload, bind mount ke kode sumber, dan tool debug. Image production justru kebalikannya, sengaja kurus dan tertutup.

Image development
  • Hot reload & rebuild cepat
  • Bind mount ke kode sumber host
  • Shell, debugger, tool jaringan ikut
  • Berjalan root demi kenyamanan
Image production
  • Satu biner statis, tanpa shell
  • Base distroless yang dipin
  • Permukaan serang minimal
  • Berjalan sebagai user non-root
⚠️Jangan kirim image dev ke production

Image dev membawa tool dan bind mount yang tidak ada di server, sehingga rawan dan tidak deterministik; bangun image production terpisah lewat multistage.

Memindai sebelum rilis

Jadikan pemindaian bagian dari pipeline, bukan ritual sesekali. Pindai image hasil build, dan jangan sertakan secret di dalam layer (suntikkan via environment atau secret manager saat runtime).

Terminal
docker scout cves ghcr.io/owner/skincare-api:9f3a1c2 docker scout quickview ghcr.io/owner/skincare-api:9f3a1c2 trivy image ghcr.io/owner/skincare-api:9f3a1c2

Dua tool ini sering dipakai berdampingan, bukan saling menggantikan, karena cakupannya berbeda.

Docker Scout
  • Native di Docker CLI dan Docker Desktop, mulus untuk alur Docker.
  • Fokus pada CVE image dan rekomendasi base image.
  • Cepat dipakai tanpa pemasangan tambahan.
Trivy
  • Cakupan CVE sangat luas, populer di kalangan power-user.
  • Bisa memindai IaC, manifest Kubernetes, repo Git, dan secret, bukan cuma image.
  • Kontrol lebih granular untuk dipasang di CI.
⚠️Pin action CI ke commit SHA, bukan tag

Pelajaran “tag itu mutable” berlaku juga di CI. Pada Maret 2026 tag action aquasecurity/trivy-action sempat di-force-push dalam serangan rantai pasok. Di workflow CI, pin action pihak ketiga ke commit SHA penuh (mis. uses: aquasecurity/trivy-action@<sha>), bukan ke tag versi yang bisa digeser diam-diam.

Mari rangkai langkah-langkahnya jadi satu alur rilis yang bisa kamu jalankan, baik lokal maupun di CI.

Build image produksi bertag SHA

docker build -t ghcr.io/owner/skincare-api:$(git rev-parse --short HEAD) . menghasilkan image multi-stage yang kecil dengan identitas terlacak ke commit.

Pindai sebelum push

Jalankan docker scout cves atau trivy image pada tag itu; gagalkan langkah ini di CI bila ada CVE kritikal agar image rawan tidak pernah sampai registry.

Push dan catat digest

docker push mengunggah image, lalu catat digest @sha256: yang dikembalikan sebagai identitas rilis untuk deploy dan rollback.

Migration terkontrol, bukan otomatis tiap startup

Godaan besar adalah menjalankan migrasi skema saat aplikasi boot. Itu berbahaya: bila kamu menjalankan tiga replika API, ketiganya akan berlomba menjalankan migrasi yang sama, dan satu migrasi gagal bisa menahan seluruh layanan naik. Migrasi sebaiknya menjadi langkah terpisah dan terkontrol, dijalankan satu kali sebelum API yang baru menerima trafik.

🌉Jembatan: dari php artisan migrate ke tool migrasi Go

Di Laravel migrasi adalah perintah eksplisit (php artisan migrate) yang kamu jalankan sadar, bukan saat tiap request; di Go pakai pola sama dengan tool seperti golang-migrate sebagai job one-off, terpisah dari proses API.

compose.yaml
services: db: image: postgres:17 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 3s retries: 5 migrate: image: migrate/migrate depends_on: db: condition: service_healthy volumes: - ./migrations:/m command: ["-path", "/m", "-database", "${DATABASE_URL}", "up"] api: build: . depends_on: migrate: condition: service_completed_successfully

Migrasi sebagai service one-off. Service migrate selesai dulu (exit 0), baru API naik, sehingga skema selalu siap sebelum trafik masuk.

03

Ringkasan

Distribusi reproducible plus image produksi yang dikeraskan

Chapter ini membawa image dari laptop ke produksi: identitas yang terlacak lewat registry, lalu image yang dikeraskan, dipindai, dan dengan migrasi terkontrol.

Kita mulai dari tagging: latest mutable dan merusak rollback, jadi pin semver, SHA, dan referensikan digest untuk jaminan byte-identik. Registry memisahkan kapan image dibangun dari kapan dijalankan, build sekali di CI lalu artefak yang sama ditarik staging dan prod. Lalu pengerasan: image produksi kecil, non-root, dipisah dari image dev, dan dipindai dengan Docker Scout atau Trivy di pipeline, dengan disiplin “pin ke SHA” yang berlaku sampai ke action CI. Terakhir, migrasi adalah job one-off terkontrol, bukan efek samping startup.

Yang Wajib Menempel

  • Tag adalah identitas terlacak, bukan sekadar “terbaru”; latest mutable dan merusak rollback.
  • Pin semver dan SHA, referensikan digest @sha256: untuk deploy reproducible dan rollback yang pasti.
  • Build sekali di CI, tarik artefak identik di setiap lingkungan; registry memisahkan build dari run.
  • Image produksi kecil, non-root, tanpa shell, tanpa secret di layer, dan terpisah dari image dev.
  • Pindai dengan docker scout (native) atau trivy (lebih luas) di pipeline; pin action CI ke commit SHA, bukan tag.
  • Migrasi adalah langkah one-off terkontrol via service_completed_successfully, bukan dijalankan tiap startup API.

Semua kepingan sudah di tangan: image yang baik, runtime yang terkonfigurasi, stack yang dirakit, dan distribusi yang aman. Chapter 6 menyatukannya dalam satu studi kasus skincare-api yang utuh, membongkar lima pitfall khas, lalu memetakan jalan dari image lokal ke CI/CD dan AWS.