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.
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.
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.
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.
Pin tag semver atau git SHA, dan untuk jaminan penuh referensikan digest image@sha256:…; latest mutable dan membuat deploy tidak reproducible.
Tiga lapis penamaan
| Jenis | Contoh | Sifat |
|---|---|---|
| Semantic tag | skincare-api:1.4.0 | Dibaca manusia, mengikuti rilis |
| Git SHA tag | skincare-api:9f3a1c2 | Telusur balik ke commit persis |
| Immutable digest | skincare-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)
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.
| Registry | Host | Cocok untuk |
|---|---|---|
| Docker Hub | docker.io | Image publik, base image resmi |
| GitHub Container Registry | ghcr.io | Image privat menyatu dengan repo & CI |
| AWS ECR | <acct>.dkr.ecr.<region>.amazonaws.com | Deploy 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
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.
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.
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"]
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.
- Hot reload & rebuild cepat
- Bind mount ke kode sumber host
- Shell, debugger, tool jaringan ikut
- Berjalan root demi kenyamanan
- Satu biner statis, tanpa shell
- Base distroless yang dipin
- Permukaan serang minimal
- Berjalan sebagai user non-root
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).
Terminaldocker 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.
- Native di Docker CLI dan Docker Desktop, mulus untuk alur Docker.
- Fokus pada CVE image dan rekomendasi base image.
- Cepat dipakai tanpa pemasangan tambahan.
- 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.
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.
docker build -t ghcr.io/owner/skincare-api:$(git rev-parse --short HEAD) . menghasilkan image multi-stage yang kecil dengan identitas terlacak ke commit.
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.
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.
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.yamlservices: 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.
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”;
latestmutable 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) atautrivy(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.