Belajar GitHub Actions - Studi Kasus Complete Production-Grade CI/CD Pipeline
Episode 20 of 21

Belajar GitHub Actions - Studi Kasus Complete Production-Grade CI/CD Pipeline

Studi kasus lengkap arsitektur pipeline produksi dari nol: gerbang kualitas saat pull request, auto versioning, build image multi-arch, scan Trivy, deploy staging, E2E otomatis, approval gate manual, dan deploy produksi tanpa downtime. Ditutup dengan checklist kesiapan produksi.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Pendahuluan

Dari episode 0 sampai 19 kita merakit pipa-pipa CI/CD satu per satu: workflow dasar, variabel, matrix, artifact, secret, OIDC, reusable workflow, Docker, deployment, self-hosted runner, kualitas kode, security scan, environment protection, release management, dan debugging. Di episode terakhir ini kita menyatukan semuanya menjadi satu arsitektur produksi yang utuh — mulai developer menekan tombol merge sampai pelanggan menggunakan fitur baru, semuanya otomatis dengan pengamanan berlapis.

Di episode ini kita membahas:

  1. Arsitektur pipeline produksi end-to-end dan alurnya.
  2. Workflow CI saat pull request dan workflow CD besar saat kode masuk main.
  3. Checklist kesiapan produksi pipeline GitHub Actions.

Arsitektur Pipeline Produksi End-to-End

Bayangkan alur seperti jalur boarding pesawat: setiap gerbang memeriksa dokumen sebelum penumpang naik. Kalau ada satu gerbang yang dilewati, risiko menyebar ke seluruh penumpang. Pipeline kita punya lima gerbang utama:

  1. PR Created — linting, unit test, SAST CodeQL, audit dependensi.
  2. Merged to main — auto semantic versioning, build image multi-arch, scan Trivy, push GHCR, deploy staging.
  3. Automated E2E on staging — Playwright menjalankan skenario pengguna sungguhan.
  4. Manual approval gate — Lead Engineer menyetujui deployment produksi.
  5. Production deploy — zero-downtime, GitHub Release, notifikasi Slack.

Dua workflow terpisah menjaga pemisahan tanggung jawab: ci.yml untuk pull request (feedback cepat), cd.yml untuk main (rilis penuh).

Fase 1: Gerbang Kualitas saat Pull Request

Job lint, unit test, CodeQL, dan audit berjalan paralel. Semua wajib lulus sebelum branch protection mengizinkan merge — kita tidak pernah membiarkan kode rusak masuk ke main:

ci.yml - gerbang kualitas saat pull request
name: CI - Pull Request
 
on:
  pull_request:
 
permissions:
  contents: read
  security-events: write
 
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npx eslint . --max-warnings 0
 
  unit-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm test
 
  codeql:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        language: ['javascript-typescript']
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3
 
  dependency-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm audit --audit-level=high

Empat job ini adalah tembok pertama. lint menjaga gaya dan bug kecil, unit-test membuktikan logika bekerja, codeql mencari kerentanan statis, dan dependency-audit memastikan tidak ada CVE level high pada dependensi. Audit dependensi otomatis tambahan dari Dependabot (episode 16) menutup celah di luar jam kerja.

Fase 2: Rilis & Deploy Otomatis dari Main

Inilah workflow paling penting. Perhatikan rantai needs — setiap job menunggu kualitas job sebelumnya. Nilai image dipakai dari job version lewat output, sehingga seluruh pipeline memakai tag yang sama:

cd.yml - pipeline produksi lengkap dari main
name: Production Pipeline
 
on:
  push:
    branches: [main]
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
permissions:
  contents: write
  packages: write
  security-events: write
 
jobs:
  version:
    runs-on: ubuntu-latest
    outputs:
      tag: ${{ steps.version.outputs.tag }}
    steps:
      - name: Checkout kode
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: Auto semantic versioning
        id: release
        uses: google-github-actions/release-please-action@v4
        with:
          release-type: simple
          token: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Tentukan tag rilis
        id: version
        env:
          RELEASE_TAG: ${{ steps.release.outputs.tag_name }}
        run: |
          tag="$RELEASE_TAG"
          if [ -z "$tag" ]; then
            tag=$(git tag --sort=-v:refname | head -1)
          fi
          echo "tag=${tag:-v0.0.0}" >> "$GITHUB_OUTPUT"
 
  build-push:
    needs: version
    runs-on: ubuntu-latest
    steps:
      - name: Checkout kode
        uses: actions/checkout@v4
 
      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Login GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build & push multi-arch image
        uses: docker/build-push-action@v6
        with:
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
 
  scan:
    needs: build-push
    runs-on: ubuntu-latest
    steps:
      - name: Scan image dengan Trivy
        uses: aquasecurity/trivy-action@0.24.0
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
 
      - name: Upload hasil scan ke Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif
 
  deploy-staging:
    needs: [build-push, scan]
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Checkout kode
        uses: actions/checkout@v4
 
      - name: Deploy staging (rolling update)
        uses: azure/k8s-deploy@v5
        with:
          namespace: staging
          manifests: k8s/staging.yaml
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}
 
  e2e:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - name: Checkout kode
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
 
      - name: Install dependensi
        run: npm ci
 
      - name: Jalankan E2E Playwright
        run: npx playwright test
        env:
          BASE_URL: https://staging.example.com
 
  deploy-production:
    needs: e2e
    runs-on: ubuntu-latest
    environment: production
    concurrency:
      group: production
      cancel-in-progress: false
    steps:
      - name: Checkout kode
        uses: actions/checkout@v4
 
      - name: Deploy zero-downtime ke production
        uses: azure/k8s-deploy@v5
        with:
          namespace: production
          manifests: k8s/production.yaml
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.tag }}
          strategy: canary
          action: deploy
 
  release:
    needs: [version, deploy-production]
    runs-on: ubuntu-latest
    if: needs.version.outputs.tag != ''
    steps:
      - name: Buat GitHub Release dengan changelog
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.version.outputs.tag }}
          generate_release_notes: true
 
  notify:
    needs: [version, deploy-production, release]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Kirim notifikasi Slack
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
          VERSION_TAG: ${{ needs.version.outputs.tag }}
        run: |
          curl -s -X POST "$SLACK_WEBHOOK" \
            -H 'Content-Type: application/json' \
            -d "{\"text\":\"Rilis ${VERSION_TAG} berhasil dideploy\"}"

Catatan penting dari workflow di atas:

  • Job version menghitung versi dari Conventional Commits lewat release-please. Nilai context steps.release.outputs.tag_name dikirim lewat env, bukan langsung ke run — praktik anti script injection dari episode 9.
  • build-push membangun image dua arsitektur sekaligus dengan cache GitHub Actions (type=gha), lalu scan memeriksa image dengan Trivy dan menulis hasil SARIF ke Security tab.
  • Setiap job yang men-deploy mendeklarasikan environment, sehingga protection rules staging dan production berlaku otomatis.

Fase 3: E2E, Approval Gate & Zero-Downtime

Tiga mekanisme terakhir membuat pipeline ini "production-grade":

  • E2E di staging. Setelah staging ter-deploy, Playwright menjalankan skenario pengguna sungguhan terhadap BASE_URL staging, menangkap regresi yang lolos unit test seperti flow login lintas service.
  • Approval gate. Job deploy-production memakai environment production dengan required reviewers (episode 17). GitHub menghentikan job dan meminta persetujuan Lead Engineer sebelum step apa pun berjalan — momen otomasi menyerahkan kendali ke manusia.
  • Zero-downtime. Manifest memakai Deployment dengan rolling update atau canary: pod baru di-spin lebih dulu, traffic dialihkan perlahan. concurrency memastikan hanya satu deployment produksi berjalan dalam satu waktu.

Warning

Kalau memakai self-hosted runner di repository public, siapa pun bisa menjalankan kode arbitrer di runner kalian. Pipeline produksi seperti ini hanya aman di repository privat, atau dengan runner GitHub-hosted.

Checklist Kesiapan Produksi

Sebelum mengaku pipeline kalian "production-grade", cek semua lapisan berikut:

LapisanAlat di pipelineStandar minimal
Kualitas kodeESLint, unit testWajib lulus sebelum merge
SASTCodeQLScan setiap pull request
Audit dependensinpm audit, DependabotTidak ada CVE high terbuka
Versioningrelease-pleaseSemVer dari Conventional Commits
Build imageBuildx multi-archamd64 dan arm64
Image securityTrivyBlock jika HIGH/CRITICAL
RegistryGHCRPush otomatis, package permission
Deploy stagingKubernetes rollingOtomatis setelah build lulus
E2EPlaywrightSemua spec lulus
Gate produksiEnvironment productionRequired reviewers aktif
Deploy produksiZero-downtime/canaryStrategi rollback tersedia
Rilis & komunikasiGitHub Release, SlackNotifikasi otomatis per rilis

Kalau semua lapisan sudah ada, pipeline bukan lagi pengumpul tugas manual — ia menjadi sistem yang menjaga kualitas, keamanan, dan kecepatan rilis secara bersamaan.

Penutup

Di episode pamungkas ini kita merangkai seluruh seri:

  • CI di pull request: lint, unit test, CodeQL, dan audit berjalan paralel sebagai tembok pertama.
  • CD di main: auto versioning, build multi-arch, scan Trivy, push GHCR, deploy staging.
  • E2E Playwright memvalidasi staging sebelum produksi.
  • Approval gate manual dari Lead Engineer melindungi produksi.
  • Zero-downtime deploy, GitHub Release, dan notifikasi Slack menutup alur.

Selamat — kalian telah menyelesaikan perjalanan dari nol sampai pipeline produksi penuh. Jangan berhenti di sini: coba terapkan pada proyek nyata, pelajari failure mode yang tidak terduga, dan terus ukur waktu dari commit ke production. Selamat berlatih, dan sampai jumpa di seri berikutnya!

Belajar GitHub Actions - Studi Kasus Complete Production-Grade CI/CD Pipeline | Belajar GitHub Actions