Tag v1.6.0 telah duduk di origin, dan sejak chapter sebelumnya kita sudah memilih jalurnya: CI build dengan runner ephemeral. Kini tibalah pekerjaan yang sesungguhnya — menuliskan pipeline sebagai berkas yang hidup di dalam repositori, dibaca oleh GitHub Actions, dan dieksekusi di runner. Kabar baik untuk kita: seluruh kosakata yang dibutuhkan sudah dikuasai bertahap sepanjang course — build context, digest, tag, login registry — sehingga chapter ini bukan materi baru yang jatuh dari langit, melainkan penyusunan ulang kebiasaan lama ke dalam format YAML. Kita mulai dari anatomi layanannya, menulis workflow utuh build-backend.yml untuk image backend toko, membedahnya sebelas langkah, memilah empat sumber nilai yang dipakainya, lalu menyaksikan run pertamanya mendorong image ke ghcr.io.

Anatomi GitHub Actions #

GitHub Actions menyimpan seluruh pipeline di dalam repositori itu sendiri — berkas YAML di folder .github/workflows/, dengan satu berkas mewakili satu workflow. Struktur berkasnya ringkas: event menentukan kapan workflow dinyalakan, job adalah blok pekerjaan yang berjalan pada sebuah runner — mesin ephemeral yang disediakan GitHub dan sudah memasang Docker — dan setiap job tersusun dari step, yaitu langkah berurutan berupa skrip shell atau action siap pakai. Kerangka terkecil sebuah workflow terlihat seperti ini:

yaml
name: Halo Runner

on:
  push:
    branches: [main]

jobs:
  sapa:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Halo dari runner"

Mari kita telaah kerangka tersebut:

  1. on: — event push ke branch main menyalakan workflow; event lain — tag, pull request, jadwal — tinggal ditambahkan pada blok yang sama.
  2. jobs: — satu job bernama sapa berjalan pada runner ubuntu-latest; job inilah unit yang lulus atau gagal.
  3. steps: — dua langkah berurutan: actions/checkout@v4 (action resmi untuk menyalin kode ke runner) dan run: untuk mengeksekusi perintah shell di atas runner tersebut.

Workflow Utuh: build-backend.yml #

Workflow pertama toko menuntaskan satu tugas: membangun image backend dari folder backend/ dan mendorongnya ke GHCR — registry image milik GitHub yang beralamat di ghcr.io. Budi menyimpan berkas berikut sebagai .github/workflows/build-backend.yml di akar repositori:

yaml
name: Build Backend

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}/backend

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Ambil kode ke runner
        uses: actions/checkout@v4

      - name: Siapkan Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login ke GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Susun nama image dengan huruf kecil
        id: image
        run: |
          echo "name=${REGISTRY}/${IMAGE_NAME,,}" >> "$GITHUB_OUTPUT"

      - name: Build dan push image backend
        uses: docker/build-push-action@v6
        with:
          context: ./backend
          platforms: linux/amd64
          push: true
          tags: |
            ${{ steps.image.outputs.name }}:${{ github.ref_name }}
            ${{ steps.image.outputs.name }}:sha-${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Bedah Sebelas Langkah #

Mari kita telaah workflow tersebut langkah demi langkah:

  1. on: push dengan branches: [main] dan tags: ['v*'] — dua pemicu dari tabel pola pemicu chapter sebelumnya: push ke main dan setiap tag yang diawali v, sehingga v1.6.0 dan penerusnya sama-sama menyalakan workflow.
  2. env: — dua konstanta untuk seluruh workflow: alamat registry ghcr.io, dan nama image yang disusun dari konteks github.repository (budi/toko) plus sufiks /backend karena repositori toko menyimpan frontend dan backend sekaligus — hasil akhirnya ghcr.io/budi/toko/backend.
  3. permissions: — hak token dibatasi secara sadar: contents: read untuk checkout dan packages: write untuk mendorong image ke GHCR; prinsip least privilege yang sama semangatnya dengan memangkas capabilities container pada Section Keamanan Docker.
  4. Step checkout — action actions/checkout@v4 menyalin isi repositori ke runner; tanpa langkah ini, folder backend/ tidak pernah hadir di mesin.
  5. Step setup-buildx — docker/setup-buildx-action@v3 menyiapkan builder Buildx dengan driver docker-container, builder yang dibutuhkan untuk cache gha dan dorongan langsung ke registry.
  6. Step login — docker/login-action@v3 mengisi kredensial registry pada runner, padanan otomatis dari docker login manual pada Chapter Login dan Authentication; github.actor adalah nama pengguna yang memicu run.
  7. Step huruf kecil — nama image wajib huruf kecil, sedangkan github.repository dapat membawa huruf besar; ekspansi bash ${IMAGE_NAME,,} menurunkan seluruh huruf, dan hasilnya diserahkan ke langkah berikutnya lewat berkas $GITHUB_OUTPUT.
  8. context: ./backend — Dockerfile dan build context diambil dari folder backend/, bukan akar repositori; .dockerignore di folder yang sama tetap menjaga context tetap ramping sebagaimana kebiasaan Chapter Build Context.
  9. platforms: linux/amd64 — build satu platform yang sesuai arsitektur runner dan server produksi toko; membangun lintas arsitektur mengikuti pola dari Chapter Target Platform.
  10. tags: — dua tag dipasang pada setiap build: github.ref_name (bernilai main saat push branch, v1.6.0 saat push tag) dan sha-<commit> yang tidak pernah berganti isi — persis dua kolom tabel pola pemicu.
  11. cache-from dan cache-to dengan type=gha — layer cache BuildKit disimpan pada cache GitHub Actions sehingga build berikutnya dapat meneruskan pekerjaan pendahulunya; pembahasan mendalamnya menyusul pada Chapter Build Cache di CI/CD.

Empat Sumber Nilai di Workflow #

Workflow di atas mengambil nilai dari empat sumber yang berbeda, dan membedakan keempatnya mencegah kebingungan yang paling sering dialami pemula Actions. Tabel berikut memetakan sumber nilainya dan wilayah berlakunya:

SumberDari mana nilainyaScope dan sifatnya
secrets.GITHUB_TOKENtoken otomatis yang dilahirkan GitHub pada setiap runhidup hanya selama job; haknya mengikuti permissions:; tidak perlu disetel siapa pun
secrets buatan sendiri, misal PATnilai yang kita simpan sendiri di pengaturan repositoribertahan lintas run; dipakai bila butuh hak di luar repositori — misalnya login ke registry privat perusahaan
konteks github.*fakta peristiwa yang dibaca runner: repository, ref_name, sha, actorbaca-saja; sumber kebenaran tunggal tentang pemicu run
envkonstanta yang kita definisikan sendiri: REGISTRY, IMAGE_NAMEberlaku untuk seluruh workflow; tempat pertama menaruh pengaturan yang mungkin berubah

GITHUB_TOKEN layak mendapat perhatian khusus karena sifatnya yang unik: token ini lahir bersama setiap run, hanya mengenal repositori tempatnya dilahirkan, dan mati bersama job — tidak ada yang perlu menyimpan nilainya, dan tidak ada yang bisa dibocorkan dari berkas workflow. Personal access token (PAT) berperan sebaliknya: nilainya bertahan dan wilayahnya lebih luas, sehingga patut disimpan sebagai secret dan hanya dipakai ketika GITHUB_TOKEN memang tidak berwenang — contohnya login ke registry.example.com yang tidak mengenal token GitHub.

Tag Otomatis dengan metadata-action #

Bagian tags: pada workflow kita ditulis dengan tangan — dua baris yang kita pahami sepenuhnya. Ketika aturan penamaan tumbuh lebih rumit — tag semver yang melucuti awalan v, label 1.6 dan 1 yang mengikuti 1.6.0 otomatis — action resmi docker/metadata-action dapat mengambil alih pekerjaan itu: action ini membaca keadaan Git (branch, tag, event) lalu menghitung daftar tag image lengkap tanpa satu pun kondisi yang kita tulis sendiri. Pola pemicu sederhana toko belum membutuhkannya, tetapi ketika registry mulai penuh konvensi, action ini mengubah seluruh tabel pemicu chapter sebelumnya menjadi dua baris deklarasi. Kita bedah kemampuannya tuntas pada Chapter Version Tagging.

Pindah ke Registry Privat #

Workflow ini memilih ghcr.io karena image lahir di rumah yang sama dengan kodenya. Bagian penting yang harus dicatat: berpindah registry tidak mengubah struktur workflow — hanya dua titik yang bergeser. Titik pertama adalah nilai REGISTRY:

yaml
env:
  REGISTRY: registry.example.com

Titik kedua adalah langkah login: GITHUB_TOKEN tidak mengenal registry privat perusahaan, sehingga kredensialnya disimpan sebagai encrypted secrets dan dipanggil pada langkah login:

yaml
      - name: Login ke registry privat
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

Seluruh mekanisme login dan penyimpanan kredensialnya mengikuti aturan yang sama dengan Chapter Login dan Authentication — hanya alamat dan pemilik kredensial yang berubah. Server produksi toko sendiri sudah terbiasa menarik dari registry.example.com sejak Section Deployment dan Production, sehingga dua registry dapat hidup berdampingan: GHCR untuk kandidat hasil pipeline, registry privat untuk image yang benar-benar dikerahkan.

Menyaksikan Run Pertama #

Budi menyerahkan workflow itu ke repositori dengan satu commit, dan push ke main itu sendiri menjadi pemicu run pertama — workflow baru yang membaca event push langsung menyalakan dirinya sendiri. Di tab Actions, job build berjalan hijau dari awal sampai akhir; ekor log pada langkah build-push terbaca sebagai berikut:

Output yang Dihasilkan (dipotong):

text
#1 [internal] load build definition from backend/Dockerfile
#2 [internal] load metadata for docker.io/library/node:22-alpine
#3 [auth] node:22-alpine pull token from registry-1.docker.io
#4 [internal] load .dockerignore
#5 [1/9] FROM docker.io/library/node:22-alpine
...
#14 exporting to image
#14 exporting layers 1.4s done
#14 writing image sha256:e7a1c4f8b2d6903e5a7c1b3d5f7a9c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a done
#14 naming to ghcr.io/budi/toko/backend:main done
#14 naming to ghcr.io/budi/toko/backend:sha-3f9c2e8a1b7d5c4e6f8a0b1c2d3e4f5a6b7c8d9e done
#14 pushing layers 6.8s done
#14 pushing manifest for ghcr.io/budi/toko/backend:main done
#14 pushing manifest for ghcr.io/budi/toko/backend:sha-3f9c2e8a1b7d5c4e6f8a0b1c2d3e4f5a6b7c8d9e done
digest: sha256:e7a1c4f8b2d6903e5a7c1b3d5f7a9c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a

Mari kita telaah ekor log tersebut:

  1. Awalan #N — nomor langkah BuildKit dengan format yang sama persis seperti build di laptop Budi; tidak ada satu pun perilaku build yang berubah karena pindah mesin.
  2. writing image sha256:... — image lahir di runner; 64 heksadesimal itu adalah image ID lokal runner yang akan hangus bersama mesin ephemeral-nya.
  3. naming to — dua tag dipasang pada satu image yang sama: main yang bergulir dan sha-3f9c... yang permanen — dua nama untuk satu objek, persis pola pemicu.
  4. pushing manifest — image terkirim ke ghcr.io; layer yang sudah ada di registry tidak dikirim ulang, mekanisme Layer already exists yang kita saksikan pada Chapter Push dan Pull Image.
  5. Baris digest: terakhir — identitas image di registry; formatnya sama persis dengan RepoDigests yang kita baca di chapter pembuka, sehingga pipeline kini dapat mencatat digest ini sebagai bukti rilis pada run log.

Workflow pertama toko sudah hidup: setiap push ke main dan setiap tag v* melahirkan image bertanda digest di ghcr.io tanpa satu ketikan manual. Namun mari kita jujur pada bentuknya — satu job yang langsung membangun dan mendorong. Belum ada test yang menjaga pintu masuk, belum ada scan yang memeriksa isi, dan belum ada satu pun titik di mana manusia dimintai persetujuan. Ritual checklist produksi yang lengkap menuntut empat job yang berbaris: test sebelum build, build sebelum scan, dan scan sebelum deploy. Merangkai barisan itu — lengkap dengan database sidecar untuk job test — adalah pekerjaan chapter berikutnya, Build, Test, dan Publish.

Referensi #