Pertanyaan tim audit di akhir chapter sebelumnya terdengar sederhana: kalau laporan:1.2 dibangun lagi besok dari kode yang sama, apakah hasilnya image yang sama? Pertanyaan semacam ini bukan kuriositas — audit keamanan perlu membuktikan bahwa image produksi identik dengan image yang diuji, tim operasional perlu memastikan dua server menjalankan bit yang sama, dan penyelidikan insiden perlu bisa memutar ulang sejarah build dengan hasil yang persis. Sayangnya, jawaban bawaan Docker adalah tidak: dua build dari Dockerfile yang sama, tanpa satu perubahan pun, menghasilkan image dengan ID yang berbeda. Chapter penutup section ini membedah sumber-sumber ketidakpastian itu satu per satu, lalu menguncinya berlapis — pin digest, lock file, SOURCE_DATE_EPOCH, sampai attestasi provenance — sampai dua build yang sama lahir dengan IMAGE ID yang identik.
Dua Build yang Tidak Identik #
Mari kita buktikan dulu klaim pembukanya. Budi membangun myapp apa adanya, membersihkan build cache agar builder benar-benar bekerja dari nol, lalu membangun lagi tanpa menyentuh satu berkas pun:
$ docker build -t myapp:uji .
$ docker builder prune -fOutput yang Dihasilkan:
Total reclaimed space: 1.2GB$ docker build -t myapp:uji2 .
$ docker images myappOutput yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp uji e18e0476eecd About a minute ago 278MB
myapp uji2 9a9f77d65fc2 20 seconds ago 278MBMari kita telaah hasil yang memalukan itu:
- Dua IMAGE ID berbeda —
e18e0476eecddan9a9f77d65fc2; ukurannya sama, isinya “seharusnya” sama, tetapi identitasnya berbeda, dan identitas itulah yang diperiksa audit. - Kolom CREATED membocorkan biang keroknya — dua image lahir dengan selisih satu menit; waktu pembuatan tercatat di konfigurasi image, dan waktu yang berbeda otomatis menghasilkan hash konfigurasi yang berbeda, sehingga ID image ikut berubah meski isinya identik.
docker builder prunebukan penyebab, hanya pemperjelas — tanpa pembersihan cache, build kedua akan dilayani penuh dari cache dan menghasilkan ID yang sama; memangkas cache memaksa builder bekerja ulang dan menyingkapkan sifat aslinya.
Empat Sumber Non-Determinisme #
Build yang tidak bisa diulang dengan hasil identik punya beberapa sumber yang saling bertumpuk:
- Base image yang bergeser —
FROM node:22-alpinemenyebut tag, dan tag adalah penunjuk yang bisa dipindahkan penerbitnya kapan pun; dua build pada hari berbeda bisa diam-diam memakai dua isi base yang berbeda — jebakan yang kita buktikan sendiri pada Chapter Image Digest. - Stempel waktu pada layer dan konfigurasi — waktu pembuatan layer hasil
RUNserta kolomcreatedpada konfigurasi image mengikuti jam dinding saat build berjalan; inilah penyebab demo di atas. - Perintah yang membaca jam — instruksi seperti
RUN date > build.infomenuliskan waktu build ke dalam berkas yang menjadi bagian layer; setiap build melahirkan berkas — dan layer — yang berbeda. - Dependensi yang tidak dipin —
npm installtanpa lock file,apt-get installtanpa versi, ataupip installdengan rentang versi membiarkan registry menentukan versi mana yang terpasang hari itu.
Solusinya karenanya berlapis: setiap sumber harus dikunci dengan alatnya masing-masing.
Pin Base dengan Digest #
Kunci pertama dan terpenting menyangkut FROM. Satu-satunya cara menjamin base image tidak bergeser adalah menyebut digest-nya secara langsung — materi yang dibuktikan tuntas pada Chapter Image Digest:
FROM node:22-alpine@sha256:876b4dd003fc4641a962d7809db28064d7a14291ff4da8430ef347043f332084Baris di atas memuat bentuk lengkapnya: nama image, tag sebagai dokumentasi versi, lalu @sha256: diikuti 64 karakter heksadesimal yang mengunci manifest base secara permanen. Tag boleh tetap ditulis untuk keterbacaan — yang menentukan tetap digest, karena bagian setelah @ menimpa penunjuk tag. Nilai digest ini Budi ambil dari docker images --digests setelah menarik base-nya, dan seluruh ekosistem paket lain mengikuti prinsip pin yang sama:
| Ekosistem | Kunci pin | Catatan |
|---|---|---|
| Base image | FROM image@sha256:... | digest manifest, bukan tag |
| Node.js | package-lock.json + npm ci | sudah berjalan di myapp sejak Chapter Build Cache |
| Go | go.sum + go mod download | checksum setiap modul diverifikasi |
| Rust | Cargo.lock + cargo build --locked | build menolak versi yang tidak tercatat |
| Python | requirements.txt dengan == | versi eksplisit, bukan rentang >= |
| Paket sistem | apt-get install paket=versi | versi per paket ditulis eksplisit |
Menandai Versi dengan ARG dan LABEL #
Kunci kedua berlaku untuk versi aplikasi sendiri. Kebutuhannya: image yang dibangun dari commit berbeda harus bisa dibedakan tanpa membongkar isinya, dan angka versi itu harus tercatat di metadata image, bukan di teks Dockerfile yang mudah lupa disunting. Polanya memakai ARG yang justru kita legitimasikan kembali setelah chapter Secret dan SSH Mount mengecapnya bukan tempat rahasia:
ARG VERSI=2.0.0
LABEL version=$VERSINilai bawaan bisa ditimpa saat build — docker build --build-arg VERSI=2.0.1 . — tanpa menyentuh satu berkas pun di repositori. Hasilnya terbaca dari metadata image:
$ docker inspect --format '{{json .Config.Labels}}' myapp:2.0Output yang Dihasilkan:
{"version":"2.0.0"}Format {{json .Config.Labels}} pada template docker inspect menyajikan seluruh LABEL sebagai satu objek JSON — teknik format yang sudah kita kenal pada Chapter Image Inspect dan History. Dengan LABEL terpasang, dua image yang tampak sama di docker images tetap bisa dibedakan versinya dalam satu perintah.
Menstabilkan Waktu dengan SOURCE_DATE_EPOCH #
Kunci ketiga menyerang sumber non-determinisme yang paling diam-diam: waktu. BuildKit mengenali satu nama ARG istimewa, SOURCE_DATE_EPOCH, yang nilainya dibaca sebagai jumlah detik sejak epoch Unix; bila nilai itu ada, BuildKit memakainya sebagai stempel waktu bagi layer yang dibuat instruksi RUN dan bagi konfigurasi image, menggantikan jam dinding mesin build. myapp versi 2.0.0 dirilis pada 1 Oktober 2026, jadi Budi mengunci epoch tanggal itu — 1790812800 detik — langsung di Dockerfile:
FROM node:22-alpine@sha256:876b4dd003fc4641a962d7809db28064d7a14291ff4da8430ef347043f332084
ARG VERSI=2.0.0
ARG SOURCE_DATE_EPOCH=1790812800
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
LABEL version=$VERSI
EXPOSE 8000
CMD ["node", "server.js"]Mari kita telaah perubahan yang dibawa tiga baris barunya:
FROM ...@sha256:876b4dd...— base dikunci digest; base yang identik dijamin berlaku untuk kedua build kapan pun dijalankan.ARG SOURCE_DATE_EPOCH=1790812800— stempel waktu seluruh layer dan konfigurasi image dipatok ke 1 Oktober 2026 00:00:00 UTC; jam dinding mesin build kehilangan kuasa atas hasil akhir, dan instruksi sepertiRUN datepun berhenti melahirkan kejutan karena environmentSOURCE_DATE_EPOCHikut tersedia di dalamRUN.RUN --mount=type=cache,target=/root/.npm npm ci— cache mount dari Chapter Cache Mount tetap dipertahankan justru karena aman: isinya hidup di luar layer, sehingga cache yang hangat maupun kosong tidak pernah mengubah satu byte pun hasil build.LABEL version=$VERSI— nomor versi tercatat di metadata; nilaiARGyang berbeda otomatis melahirkan image yang berbeda, dan memang seharusnya begitu.
Bukti: Build Ulang, ID Sama #
Saatnya membayar tagihan klaim chapter ini. Budi mengulang ritual pembuktian yang tadi gagal — build, hapus cache total, build lagi:
$ docker build -t myapp:2.0 .
$ docker builder prune -af
$ docker build -t myapp:2.0-uji .
$ docker images myappOutput yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp 2.0 2ee42e4f06c6 12 days ago 278MB
myapp 2.0-uji 2ee42e4f06c6 12 days ago 278MBMari kita telaah buktinya:
- Dua baris, satu IMAGE ID —
2ee42e4f06c6tampil pada kedua tag meski build kedua dieksekusi penuh tanpa cache; layer-layer yang dihasilkan identik byte demi byte, sehingga hash — dan identitas — ikut identik. - CREATED 12 days ago pada keduanya — kolom ini kini menunjuk 1 Oktober 2026 (epoch yang dipin), bukan momen build dijalankan pada 13 Oktober; stempel waktu yang tadinya biang perbedaan kini menjadi bagian bukti.
npm ciyang dieksekusi ulang tetap deterministik —package-lock.jsonmematok versi setiap paket, dan mtime berkas hasil pemasangan dinormalisasiSOURCE_DATE_EPOCH; unduhan ulang tanpa cache mount tidak mengubah hasil.
Attestasi: Provenance dan SBOM #
Kunci terakhir tidak lagi berurusan dengan isi image, melainkan dengan bukti tentang bagaimana image itu dibangun. buildx mampu melampirkan dua attestasi otomatis pada saat push: provenance — catatan terstruktur berisi sumber build, parameter, dan identitas builder — dan SBOM — daftar seluruh paket yang terkandung dalam image. Keduanya menyala lewat dua flag:
$ docker buildx build --provenance=mode=max --sbom=true \
-t registry.example.com/myapp:2.0 --push .Output yang Dihasilkan:
[+] Building 13.8s (20/20) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 389B 0.0s
=> CACHED [1/6] FROM docker.io/library/node:22-alpine@sha256:876b4dd003fc4641a962d7809db28064d7a14291ff4da8430ef347043f332084 0.0s
...
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> exporting manifest list sha256:f133b06bcc88d1ddca727304a1ea57f24981da880836821bbfd9d02dbf6b491e 0.4s
=> => naming to registry.example.com/myapp:2.0 0.0s
=> pushing manifest list 1.7sHasil push-nya bukan lagi satu image polos melainkan manifest list yang memuat image ditambah attestation-nya — dan docker manifest inspect, yang kita kenal dari Chapter Target Platform, memperlihatkan strukturnya:
$ docker manifest inspect registry.example.com/myapp:2.0Output yang Dihasilkan:
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 739,
"digest": "sha256:3c6e69f8ea5cd004b5a5a8843d4a8051c2750f6591727fbbc3625d4df22cef9b",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 1254,
"digest": "sha256:3c348abdad844c6265fa057090eaf3e8d4c817d72170dcbeeb2a3aa511178363",
"platform": {
"architecture": "unknown",
"os": "unknown"
},
"annotations": {
"vnd.docker.reference.digest": "sha256:3c6e69f8ea5cd004b5a5a8843d4a8051c2750f6591727fbbc3625d4df22cef9b",
"vnd.docker.reference.type": "attestation-manifest"
}
}
]
}Mari kita telaah dua entri di dalamnya:
- Entri pertama: image myapp untuk
linux/amd64— manifest image biasa, persis entri-entri manifest list katalog pada Chapter Target Platform; inilah yang dijalankandocker run. - Entri kedua: platform
unknown/unknown— bukan image yang bisa dijalankan, melainkan attestation manifest; anotasivnd.docker.reference.type: attestation-manifestmenyatakan jenisnya, danvnd.docker.reference.digestmenunjuk image yang dideskripsikannya. - Provenance dan SBOM hidup di dalam entri kedua — metadata build dan daftar paket tersimpan sebagai objek terpisah yang menempel pada image di registry; pemindai kerentanan dan tool audit membacanya tanpa harus menjalankan image.
Satu catatan penutup untuk bahasa kompilasi: binary punya non-determinisme tambahan berupa path absolut direktori build yang ditanam compiler ke dalam berkas hasil. Go menyuapi obatnya lewat flag -trimpath — go build -trimpath mengganti path mesin build dengan path modul, sehingga binary yang dikompilasi di folder mana pun identik; katalog sudah memakai flag ini sejak Dockerfile-nya dirakit, dan kini kita tahu alasan di baliknya.
Section Image Build dan Optimisasi sampai di sini tuntas perjalanannya: build yang cepat lewat urutan instruksi dan cache mount, rahasia yang tidak membekas, image yang menyeberangi arsitektur, ukuran yang ramping, dan kini identitas yang bisa diverifikasi ulang. Produk section ini — image yang baik — hanyalah separuh kebenaran Docker, karena image mendeskripsikan apa yang dijalankan, bukan bagaimana container menjalankannya. Environment variable, perintah start, user, permission, dan seluruh knob runtime itulah wilayah Section Konfigurasi Container, yang dibuka oleh Chapter Command Override.
Referensi #
- Image Attestations — mekanisme provenance dan SBOM yang dilampirkan buildx pada image.
- Dockerfile Reference
— sintaks
ARG,LABEL, dan perilakuFROMdengan digest. - Dockerfile Best Practices — panduan pin versi dan kebiasaan build yang deterministik.
- SLSA — kerangka standar provenance rantai pasok yang menjadi acuan attestasi.