Mengganti builder lama yang lambat dengan BuildKit dan Buildx: build paralel dan cache lanjutan, build secrets yang tidak pernah mengendap di layer image, serta produksi image multi-platform (amd64 + arm64) dalam satu perintah dengan provenance dan SBOM bawaan.

Setelah di episode 17 sebelumnya kita men-deploy service ke Swarm, melepaskan update tanpa downtime, dan menyimpan rahasia dengan secrets — kalian mungkin mulai merasakan ketidakadilan: cluster sudah modern, tapi mesin pembuat image-nya masih yang kuno. Hampir semua perintah docker build yang kita pakai sejauh ini berjalan di atas legacy builder — arsitektur yang sudah berumur dan kini resmi deprecated. Di episode ini kita menggantinya: mengenal BuildKit, mesin build generasi baru, dan Buildx, CLI yang mengendalikannya.
Kenapa ini penting, dan bukan sekadar "upgrade teknis"? Karena di dunia nyata, waktu build adalah uang: setiap menit build yang memakan waktu adalah menit yang tidak dipakai untuk mengirimkan perbaikan, dan di CI/CD (episode 19) build yang lambat memperlambat seluruh pipeline. BuildKit menghadirkan tiga hal yang langsung terasa: (1) eksekusi paralel — tahap build yang independen dikerjakan bersamaan, bukan berurutan; (2) cache lanjutan — termasuk cache yang bisa dibawa ke mesin lain; (3) build secrets — data rahasia yang dipakai saat build namun tidak pernah tertanam di layer image, sekaligus fondasi untuk image multi-platform — satu tag, banyak arsitektur, dari satu perintah.
Bayangkan legacy builder sebagai jalur produksi satu baris di pabrik: setiap komponen harus melewati satu stasiun yang sama, bergantian, dan setiap proses ulang dimulai dari nol. BuildKit adalah pabrik dengan banyak jalur paralel, gudang penyimpanan hasil antara, dan brankas terpisah untuk material rahasia. Pabrik mana yang ingin kalian miliki saat pesanan mulai menumpuk? Di episode ini kita akan membangunnya.
Dua istilah ini sering tertukar, jadi mari pisahkan dulu:
docker buildx) yang mengendalikan BuildKit. Ia menyediakan fitur yang tidak ada di docker build klasik: multi-builder, multi-platform, dan manajemen cache eksplisit.Sejak Docker 23.0, docker build secara default sudah memakai BuildKit melalui buildx. Kalian bisa verifikasi dengan:
docker buildx version
docker buildx lsNAME/NODE DRIVER ENDPOINT STATUS BUILDKIT PLATFORMS
default* docker default running v0.15.1 linux/amd64, linux/arm64Builder default memakai driver docker — artinya BuildKit berjalan di dalam daemon Docker yang sama. Ini cukup untuk sebagian besar kebutuhan. Nanti kita tambahkan builder khusus untuk multi-platform.
Legacy builder mengeksekusi Dockerfile linier: instruksi demi instruksi, setiap RUN dijalankan dalam container sementara, hasilnya di-commit menjadi layer. Ia memiliki dua kelemahan struktural: tidak bisa mengerjakan dua tahap yang independen sekaligus, dan cache-nya lokal (tidak bisa dibawa ke mesin lain). BuildKit memperbaikinya lewat empat fitur yang akan kalian pakai setiap hari:
RUN yang berbeda, atau dua stage multi-stage build yang tidak saling bergantung) dikerjakan bersamaan. Build multi-stage yang tadinya 2 menit bisa turun ke 1 menit tanpa mengubah satu baris pun.--cache-from/--cache-to) sehingga build berikutnya di mesin lain lompat ke tahap yang tidak berubah.RUN --mount=... memberi akses sementara ke file/rahasia tanpa pernah menuliskannya ke layer image — mengakhiri era secret yang bocor lewat ARG/ENV (yang kita bahas di episode 6 dan 26).docker/dockerfile:1 = dockerfile.v1, :0.4 = eksperimental). BuildKit memisahkan "mesin" dari "bahasa" — alasan kenapa fitur Dockerfile baru (seperti COPY --link) bisa rilis tanpa upgrade daemon.Note
Legacy builder tidak hanya deprecated — ia sudah dihapus dari daemon Docker modern (sejak Docker Engine 23.0, DOCKER_BUILDKIT=0 tidak lagi didukung). Kalian tidak sedang memilih untuk memakai BuildKit; kalian sedang belajar memakai mesin yang sudah menjadi satu-satunya opsi. Perbedaan yang perlu diingat hanyalah fitur dan flag mana yang eksklusif Buildx.
Inilah fitur yang paling sering disalahpahami, jadi kita mulai dari sini. Kebutuhan klasik: membangun image yang butuh mengunduh dependency dari registry privat (npm, Maven, Go proxy) yang butuh token. Cara naif — menaruh token di ARG atau ENV — menanamkannya di layer image. Siapa pun yang punya image bisa membaca token itu dengan docker history. BuildKit menyelesaikannya dengan secret mount: token hanya tersedia selama eksekusi RUN, dan hilang begitu perintah selesai.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token \
NODE_AUTH_TOKEN="$(cat /run/secrets/npm_token)" \
npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]docker build \
--secret id=npm_token,src=./.npm-token \
-t my-app:1.0 .Bedahnya: RUN --mount=type=secret,id=npm_token membuat file /run/secrets/npm_token tersedia hanya di dalam container untuk instruksi RUN itu. Setelah selesai, file tidak ada — dan karena BuildKit tidak pernah meng-commit isi secret ke layer, docker history bersih. Ini bukan sekadar praktik baik; ini perbedaan antara bocor dan tidak bocor. Kalian bisa membuktikannya: docker history my-app:1.0 tidak akan menampilkan token.
Masalah klasik kedua: setiap kali kode berubah, seluruh npm ci/apt-get install/go mod download dijalankan ulang — meskipun dependency-nya tidak berubah. Cache mount memisahkan data yang sering berubah (hasil unduhan dependency) dari layer image, sehingga unduhan bertahan antar build tanpa menggelembungkan layer:
FROM node:20-alpine AS node-stage
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
FROM golang:1.22-alpine AS go-stage
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
FROM ubuntu:24.04 AS apt-stage
RUN rm -f /etc/apt/apt.conf.d/docker-clean; \
--mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y curlPerhatikan polanya: RUN --mount=type=cache,target=/path membuat direktori /path bertahan antar build di mesin yang sama (cache BuildKit), tapi isinya tidak pernah menjadi bagian dari layer image. Hasilnya: build kedua dan seterusnya melewati unduhan, tanpa membesarkan image final. Ini penghematan menit yang terasa nyata di CI.
Untuk mengambil hasil dari stage lain saat build berjalan — misalnya memakai binary dari stage builder — COPY --from (episode 7) sudah familier. BuildKit memberi alternatif yang lebih hemat untuk beberapa kasus: RUN --mount=type=bind,from=<stage>, yang me-mount stage sebagai sumber tanpa menyalin semuanya:
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .
FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY --from=builder --link /out/app /usr/local/bin/app
ENTRYPOINT ["app"]Kombinasi --link pada COPY (fitur BuildKit) menyalin hanya file yang berubah antar build — layer yang tidak berubah di-reuse, mempercepat siklus "ubah kode → build".
Ini fitur yang membuat kalian tidak perlu menjalankan 10 mesin untuk 10 arsitektur. Dengan buildx, satu perintah menghasilkan image linux/amd64, linux/arm64, dan lainnya — digabung dalam satu tag manifest list. Ketika docker pull dijalankan di mesin arm64, Docker mengambil varian yang tepat secara otomatis.
Langkah pertama: buat builder instance yang mendukung multi-platform. Driver docker bawaan hanya bisa membangun untuk platform host. Builder khusus memakai driver docker-container — BuildKit berjalan di container terpisah, bisa membangun untuk platform mana pun (dengan emulasi QEMU bila host tidak punya node native):
docker buildx create \
--name multiarch \
--driver docker-container \
--bootstrap \
--use
docker buildx ls--bootstrap segera menarik image BuildKit container, dan --use menjadikannya builder aktif. Sekarang bangun image Go untuk dua arsitektur sekaligus — ini contoh end-to-end dari seluruh episode ini:
FROM golang:1.22-alpine AS builder
WORKDIR /src
RUN apk add --no-cache ca-certificates
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/app .
FROM alpine:3.20
COPY --from=builder --link /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder --link /out/app /usr/local/bin/app
ENTRYPOINT ["app"]docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/arman/my-go-app:1.0.0 \
--provenance true \
--sbom true \
--push .Inilah inti episode ini dalam satu perintah: dua arsitektur, satu tag, plus dua metadata yang datang gratis dari BuildKit — --provenance (jejak bangunan: siapa membangun, dari kode apa, dengan mesin apa — fondasi supply chain) dan --sbom (Software Bill of Materials, yang kita pelajari di episode 14). Keduanya ter-bit ke registry bersama image, tanpa tool tambahan.
Important
Multi-platform memerlukan --push. Builder docker-container tidak bisa --load lebih dari satu platform ke daemon lokal (--load hanya menangani satu, biasanya host). Untuk menyebarkan image multi-arch, kirim langsung ke registry dengan --push — lalu tarik dengan docker pull dari mesin target. Jangan bingung melihat --load gagal untuk multi-platform: ini perilaku yang didesain, bukan bug.
Untuk memverifikasi bahwa satu tag benar-benar berisi banyak arsitektur, docker buildx imagetools membaca manifest list di registry tanpa perlu menarik image:
docker buildx imagetools inspect ghcr.io/arman/my-go-app:1.0.0Output akan mencantumkan daftar platform beserta digest tiap varian — bukti bahwa tag 1.0.0 melayani linux/amd64 dan linux/arm64 sekaligus. Ini juga alat yang berguna untuk memastikan image yang diklaim multi-arch benar-benar multi-arch sebelum dipakai, dan untuk membaca provenance/SBOM yang menyertai image dari episode 14.
Flag buildx dipakai di docker build legacy. Beberapa fitur eksklusif buildx (--secret, --platform, --push, --cache-to) tidak ada di docker build klasik. Saat memakai Dockerfile memakai --mount, pastikan memanggilnya lewat docker buildx build atau docker build (yang kini BuildKit) — bukan daemon lama.
Kecepatan emulasi QEMU untuk arm64. Membangun linux/arm64 di host amd64 memakai emulasi — bisa 2-5x lebih lambat daripada build native. Solusinya: gunakan runner CI arm64 native (misal GitHub Actions ARM), atau bangun tiap arsitektur di node native masing-masing lalu gabungkan manifestnya. Untuk pembuatan sekali-sekali, emulasi masih sangat praktis.
--load multi-platform gagal. Seperti di atas — gunakan --push untuk banyak platform. --load hanya untuk single platform ke daemon lokal.
Secret bocor lewat ARG/ENV. --mount=type=secret menyelesaikan ini, tapi hanya jika kalian tidak juga menuliskannya ke ENV. Jangan menggabungkan secret dengan ENV — sekalinya masuk env, ia mengendap di layer.
Cache tidak terpakai di mesin lain. Cache BuildKit default lokal. Untuk CI atau build di banyak mesin, ekspor cache ke registry (--cache-to type=registry) — kita gunakan ini di episode 19.
Pada episode 18 ini kita telah mengganti mesin build: membedakan BuildKit (mesin) dan Buildx (CLI), memahami mengapa legacy builder mati dan fitur apa yang kita peroleh (eksekusi paralel, cache lanjutan, frontend dockerfile.v1), memakai build secrets (RUN --mount=type=secret) yang tidak pernah meninggalkan jejak di layer image, menghemat waktu dengan cache mount untuk npm/apt/Go, mengimpor stage dengan --mount=type=bind,from=... dan COPY --link, serta memproduksi image multi-platform (linux/amd64,linux/arm64) dalam satu perintah dengan --provenance dan --sbom bawaan.
Inti yang harus kalian bawa:
--mount=type=secret; data unduhan berulang → --mount=type=cache.docker buildx build --platform ... --push .; ingat multi-platform butuh --push.--provenance dan --sbom menyala gratis untuk supply chain (episode 14).Kalian kini punya image yang dibangun dengan cepat, aman dari secret bocor, dan siap untuk semua arsitektur. Pertanyaan berikutnya: siapa yang membangunnya, dan kapan? Di episode 19 selanjutnya kita akan otomatiskan semuanya — Integrasi Docker dalam CI/CD Pipeline — menyambungkan build, test, scan, dan push ke GitHub Actions, mengoptimalkan cache di pipeline, dan memahami DinD vs DooD agar kalian tidak membangun labirin keamanan di runner. Sampai jumpa di episode 19!