Build toko kini selesai dalam sembilan puluh detik, dan tab Actions berubah dari sumber frustasi menjadi tempat yang menyenangkan untuk dikunjungi. Namun kecepatan itu menutupi satu kebohongan kecil yang kita warisi dari pipeline lama: langkah docker scout pada Chapter Build, Test, dan Publish memang menjalankan pemindaian, tetapi hasilnya hanya dicetak ke log — job tetap hijau berapa pun banyaknya kerentanan kritis yang ditemukan. Pemindaian yang tidak pernah bisa menggagalkan apa pun bukanlah gerbang; pemindaian itu hanyalah laporan yang dibaca orang saat ingat. Chapter ini memberikan pemindaian daya untuk berkata “tidak”: kebijakan exit-code yang membuat job gagal saat temuan melampaui ambang, alur tiga job yang menahan tag cantik sampai gerbang lolos, kebijakan toleransi yang tertulis beserta pengecualiannya, serta SBOM yang dijadikan artefak rilis untuk audit antar versi.
Gerbang, Bukan Laporan #
Gerbang adalah pemeriksaan yang bisa menggagalkan pipeline, dan mekanismenya memakai kontrak tertua antara program dan sistem yang menjalankannya: exit code. Program keluar dengan 0 berarti sukses; keluar dengan nilai lain berarti gagal — dan GitHub Actions menerjemahkan nilai bukan nol menjadi job merah, persis kontrak yang kita bedah pada Chapter Memahami Exit Code. Pemindaian lama toko tidak punya daya menolak justru karena selalu keluar dengan 0: tabel temuan dicetak, lalu job ditandai sukses. Docker Scout memiliki flag khusus untuk mematahkan kebiasaan itu: --exit-code membuat perintah keluar dengan kode 2 saat ada temuan yang lolos filter, dan kode 2 itulah yang mengubah laporan menjadi gerbang.
Struktur workflow juga ikut berubah. Tag main dan latest tidak lagi lahir langsung dari job build; image terlebih dahulu menunggu di satu alamat netral — tag sha — seperti penumpang di ruang tunggu sebelum diberi boarding pass. Job scan memeriksa image di ruang tunggu itu; hanya image yang lulus yang berpindah ke job publish untuk menerima nama-nama manisnya. Alur lengkapnya tampak pada diagram berikut:
Mari kita telaah dua jalur pada diagram tersebut: jalur kiri adalah hari biasa — scan bersih, publish berjalan, tag main bergeser ke image baru. Jalur kanan adalah hari yang kita siapkan — scan menemukan kritis, job gagal, dan seluruh job yang bergantung padanya dilewati otomatis oleh GitHub Actions; image bermasalah tidak pernah menerima nama yang membuatnya terlihat seperti rilis resmi.
Workflow Tiga Job #
Bentuk lengkap workflow dengan tiga job yang dirantai berjalan sebagai berikut:
name: pipeline
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
sha-pendek: ${{ steps.sha.outputs.pendek }}
steps:
- uses: actions/checkout@v4
- id: sha
run: echo "pendek=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build dan dorong tag sha
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/budi/toko:sha-${{ steps.sha.outputs.pendek }}
cache-from: type=registry,ref=ghcr.io/budi/toko:buildcache
cache-to: type=registry,ref=ghcr.io/budi/toko:buildcache,mode=max
scan:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pindai CVE kritis
run: |
docker scout cves \
--only-severity critical \
--exit-code \
ghcr.io/budi/toko:sha-${{ needs.build.outputs.sha-pendek }}
publish:
needs: [build, scan]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pasang tag cantik setelah gerbang lolos
run: |
docker pull ghcr.io/budi/toko:sha-${{ needs.build.outputs.sha-pendek }}
docker tag ghcr.io/budi/toko:sha-${{ needs.build.outputs.sha-pendek }} ghcr.io/budi/toko:main
docker push ghcr.io/budi/toko:mainMari kita telaah lima poin alur pada workflow di atas:
- Job build mendorong tag
shasaja — tujuh karakter pertama SHA commit menjadi alamat byte yang stabil; image menunggu di alamat itu seperti di ruang tunggu, tanpa nama yang menjanjikan apa pun kepada konsumen. Pull request pun mendapat perlakuan sama: diuji dan dipindai tanpa pernah menyentuh tagmain. - Job scan membaca image dari registry, bukan dari disk runner —
docker scout cvesmenarik manifest dari GHCR berdasarkan tagsha, sehingga yang diperiksa persis byte yang diperiksa identitasnya; flag--only-severity criticalmembatasi hitungan ke temuan kritis, dan--exit-codemengubah keberadaan temuan menjadi keputusan. needsmerangkai urutan dan menegakkan konsekuensi — scan menunggu build selesai; publish menunggu keduanya. Ketika scan gagal, GitHub Actions otomatis melewatkan seluruh job yang bergantung padanya, tanpa satu baris logika tambahan dari kita.- Output
sha-pendekmelintas antar job lewatoutputs— nilai yang dihitung pada step job build diekspos sebagai output job, lalu dibaca job lain melaluineeds.build.outputs; pola ini menjaga satu sumber kebenaran untuk alamat image di seluruh workflow. - Tag cantik menjadi milik job publish —
mainhanya lahir setelah gerbang lolos, lewatdocker pull,docker tag, dandocker pushpada image yang sama; inilah invarian yang dijaga seluruh struktur: nama yang manis hanya melekat pada image yang lulus pemeriksaan.
Saat Gerbang Menutup #
Suatu Senin, merge sebuah pull request membawa kenaikan dependensi transitif, dan untuk pertama kalinya sejak gerbang dipasang, jalur kanan diagram benar-benar dijalankan. Log job scan berbunyi:
Output yang Dihasilkan (dipotong):
### Overview
target:
└─ ghcr.io/budi/toko:sha-4d8e1c9
digest sha256:6f2a9e1b7c4d8305f1a9c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5
os alpine 3.20
size 142MB
packages 118
...
### CVEs found: 28 (1C 2H 8M 17L)
✗ 1C 2H 8M 17L
CVE ID SEVERITY AFFECTED PACKAGE FIXED IN
CVE-2026-11875 CRITICAL libssl3 3.3.2-r0
...
Error: Process completed with exit code 2.Mari kita telaah isi kegagalan tersebut:
✗ 1C 2H 8M 17L— rekap temuan setelah seluruh isi image diinventarisasi: satu CRITICAL, dua HIGH, delapan MEDIUM, tujuh belas LOW; tanda silang mengawali baris karena ada kategori yang memicu keputusan.CVE-2026-11875padalibssl3denganFIXED IN 3.3.2-r0— temuan kritis dengan versi perbaikan yang tersedia; kolom FIXED IN mengubah temuan menjadi pekerjaan yang jelas, persis pembacaan tabel CVE dari Chapter Image Scanning.exit code 2— bahasa Docker Scout untuk “ada temuan yang memenuhi filter”; nilai khusus ini membedakan “ditemukan kerentanan” (2) dari “perintah itu sendiri gagal” (1).Error: Process completed with exit code 2.— GitHub Actions menerjemahkan exit code bukan nol menjadi kegagalan job; baris penutup yang akrab itu adalah seluruh jembatan antara scanner dan sistem CI.- Job publish tidak menghasilkan log sama sekali — tidak ada
docker tag, tidak adadocker push; tagmaintidak bergeser, dan siapa pun yang menarikghcr.io/budi/toko:mainmalam itu masih menerima image yang lama dan sudah teruji.
Perbaikannya sendiri berlangsung singkat keesokan paginya: Budi menaikkan paket yang membawa libssl3 ke 3.3.2-r0, mendorong commit baru, dan pipeline hijau kembali — tag sha yang baru lolos gerbang, lalu menerima tag main. Siklus gagal-perbaiki-lolos inilah bentuk kerja harian yang sehat dari sebuah gerbang: merah sesaat, bukan merah selamanya.
Kebijakan yang Tertulis #
Exit-code hanya mengeksekusi kebijakan; kebijakannya sendiri harus tertulis agar keputusan bisa diaudit dan diperdebatkan tanpa membaca workflow. Tabel berikut adalah kebijakan toko, memperluas kerangka ambang dari Chapter Image Scanning:
| Temuan | Aksi pipeline | Alasan |
|---|---|---|
| CRITICAL dengan versi perbaikan tersedia | gagal — gerbang menutup | perbaikan tersedia; menolak adalah murah |
| CRITICAL tanpa versi perbaikan | pengecualian tercatat dengan alasan dan tanggal kedaluwarsa | tidak bisa diperbaiki dengan upgrade; keputusan diterima secara sadar dan sementara |
| HIGH, jumlah 5 atau kurang | lulus dengan laporan | wajib tiket perbaikan pada siklus berikutnya |
| MEDIUM dan LOW | lapor saja | dipantau, dibereskan bertahap |
Baris pertama ditegakkan langsung oleh --only-severity critical --exit-code. Baris kedua tidak bisa ditegakkan scanner — cacat tanpa perbaikan tidak hilang dengan upgrade — sehingga jalurnya adalah dokumen pengecualian yang mencantumkan alasan dan tanggal kedaluwarsa, persis disiplin yang kita tanam di Chapter Image Scanning. Baris ketiga ditegakkan dengan menyaring keluaran JSON scanner lalu menghitungnya sebelum memutuskan; ambang HIGH 5 adalah pilihan toko, bukan hukum alam. Baris keempat sengaja tidak memblokir — gerbang yang memblokir segalanya akan dibongkar orang.
Alternatif: trivy-action #
Docker Scout bukan satu-satunya penjaga pintu. Trivy — pemindai yang kita kenal di Chapter Image Scanning — memiliki action resmi di marketplace yang membungkus pola yang sama dalam satu langkah:
- name: Pindai dengan Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/budi/toko:sha-${{ needs.build.outputs.sha-pendek }}
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: 1
format: tableMari kita telaah empat input yang membentuk kebijakan pada blok tersebut: severity: CRITICAL,HIGH memperluas filter menjadi dua tingkat sekaligus; ignore-unfixed: true mengecualikan temuan tanpa versi perbaikan dari hitungan, sehingga baris kedua tabel kebijakan ditegakkan oleh alatnya sendiri; exit-code: 1 memilih kode kegagalan ketika ambang tersentuh; dan format: table menjaga laporan tetap terbaca manusia di log. Prinsip kerjanya identik dengan Scout — scanner, filter, ambang, exit code — sehingga mengganti alat tidak mengubah bentuk gerbang.
SBOM sebagai Artefak Rilis #
Gerbang menjawab pertanyaan “boleh lewat atau tidak”; SBOM menjawab pertanyaan yang berbeda — “image ini sebenarnya berisi apa”. Inventaris paket yang menjadi fondasi pemindaian pada Chapter Image Scanning paling murah dilahirkan sebagai attestation bersama build: satu input tambahan pada job build, dan builder menyusun inventarisnya sendiri lalu menyimpannya menempel pada manifest image — mekanisme attestation yang kita bedah pada Chapter Image Provenance.
- name: Build dan dorong tag sha
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/budi/toko:sha-${{ steps.sha.outputs.pendek }}
sbom: true
cache-from: type=registry,ref=ghcr.io/budi/toko:buildcache
cache-to: type=registry,ref=ghcr.io/budi/toko:buildcache,mode=maxSBOM juga bisa dikeluarkan sebagai berkas lepas untuk diarsipkan. Trivy mengerjakannya dengan satu perintah, lalu workflow mengunggah hasilnya sebagai artefak:
$ trivy image --format cyclonedx --output sbom.json ghcr.io/budi/toko:sha-b62d914 - uses: actions/upload-artifact@v4
with:
name: sbom-rilis
path: sbom.jsonArsip itu baru menunjukkan taringnya saat dibandingkan antar rilis — kebiasaan diff SBOM yang kita tanam pada Chapter Supply Chain Security:
$ diff <(jq -r '.components[].name' sbom-v1.4.0.json | sort) \
<(jq -r '.components[].name' sbom-v1.5.0.json | sort)Output yang Dihasilkan:
14a15
> sharpMari kita telaah dua baris output tersebut: format 14a15 menyatakan satu entri baru ditambahkan setelah baris keempat belas, dan entri itu bernama sharp. Paket pemroses gambar itu tidak disebut satu pun berkas pada pull request rilis 1.5.0 — ia datang sebagai tamu transitif dari kenaikan dependensi. Ditemukan sebelum rilis, tamu seperti ini cukup ditanyakan satu kalimat pada penulis pull request; ditemukan setelah insiden, ia menjadi pertanyaan yang jauh lebih mahal. Diff SBOM menjadikan pertanyaan itu rutin dan murah.
Anatomi Sebuah Gerbang #
Seluruh bagian chapter ini menyusun satu gerbang dengan anatomi yang bisa dibaca ulang pada tabel berikut:
| Elemen | Perannya | Wujud pada pipeline toko |
|---|---|---|
| Scanner | menghitung temuan dari isi image | docker scout cves membaca image di registry |
| Filter | memilih temuan yang ikut dihitung | --only-severity critical |
| Ambang | memutuskan lolos atau gagal | --exit-code — satu temuan kritis cukup menutup |
| Sinyal gagal | menerjemahkan keputusan ke sistem CI | exit code 2 menjadi job merah |
| Rantai | menjaga keputusan sampai ke hilir | publish bergantung pada scan lewat needs |
| Pengecualian | lubang yang disengaja, tercatat, dan berkedaluwarsa | dokumen kebijakan di luar workflow |
Anatomi ini sengaja dibuat generik: ganti scanner, ganti filter, ganti ambang — keenam elemen tetap berdiri dengan peran yang sama. Gerbang toko kini berdiri tegak di tengah pipeline, dan image yang melewatinya layak menerima nama. Justru di titik itu masalah berikutnya muncul: nama yang dijanjikan job publish — main, latest, angka versi — masih diketik oleh tangan manusia pada baris-baris workflow, dan tangan manusia adalah mesin tagging yang paling tidak andal. Chapter Version Tagging menghapus ketikan itu: tag dihitung langsung dari keadaan Git, bukan dari ingatan orang yang sedang menekan tombol merge.
Referensi #
- Docker Scout cves
— tabel CVE beserta flag
--only-severitydan--exit-code. - SBOM attestation — pembuatan SBOM sebagai attestation yang lahir bersama build.
- Using jobs in a workflow
— urutan job dengan
needsdan perilaku job dependen saat sebuah job gagal.