CI/CD dengan GitHub Actions

14 min readIntermediate
CI/CDGitHub ActionsDevOpsDeployment

Pernahkah Anda mengalami momen klasik ini: kode Anda berjalan mulus di laptop, lalu setelah di-merge ke branch utama, aplikasi langsung rusak di server? Fenomena itu sering disebut "works on my machine" — dan itu bukan lelucon, melainkan masalah nyata yang dihadapi hampir semua tim ketika build, test, dan deploy masih dilakukan manual. GitHub Actions hadir untuk memecahkan masalah itu: sebuah platform yang memungkinkan Anda mengotomatiskan build, test, dan deployment langsung dari dalam repository GitHub Anda.

Artikel ini akan membawa Anda memahami konsep CI/CD secara utuh, lalu menyusun pipeline nyata untuk aplikasi Java Spring Boot — dari sekadar mvn verify di setiap push, hingga membangun image Docker di branch main. Artikel ini adalah kelanjutan logis dari Dockerizing Java Applications: di artikel itu kita belajar membuat image yang baik, dan di sini kita belajar cara membangun serta mengirim image itu secara otomatis.

Apa itu CI/CD dan Kenapa Penting

Continuous Integration (CI) dan Continuous Delivery (CD) adalah dua praktik DevOps yang saling berkaitan. Mari kita bedah satu per satu.

Continuous Integration (CI) berarti setiap perubahan kode yang masuk ke repository — sekecil apa pun — langsung di-build dan di-test secara otomatis. Tujuannya sederhana: tangkap masalah sedini mungkin, sebelum masalah itu merambat ke tim lain. Jika ada test yang gagal karena perubahan Anda, Anda tahu dalam hitungan menit, bukan setelah kode itu merusak produksi.

Continuous Delivery (CD) adalah langkah berikutnya: setelah kode lolos semua test, kode itu otomatis disiapkan untuk dirilis — dikemas, di-upload, dan siap di-deploy ke staging atau produksi. Pada tahap ini, keputusan men-deploy ke produksi bisa tetap melibatkan manusia (misalnya lewat tombol approval), tetapi seluruh proses teknisnya sudah otomatis.

AspekManual (tanpa CI/CD)Dengan CI/CD
BuildDijalankan sendiri di laptop masing-masingOtomatis di server (runner) setiap ada perubahan
TestSering dilupakan, hasilnya berbeda antar mesinSelalu dijalankan, hasilnya konsisten dan terdokumentasi
Detect bugTerlambat, sering baru ketahuan di produksiCepat, langsung saat kode di-push
DeployCopy-paste JAR via SSH, rawan salah langkahOtomatis, terukur, bisa di-rollback
DokumentasiTersebar di kepala orang dan chatTertulis rapi sebagai file konfigurasi

CI tanpa CD membuat Anda selalu punya kode yang sehat tapi tidak pernah sampai ke pengguna. CD tanpa CI adalah memproduksi barang tanpa menguji kualitasnya. Keduanya paling kuat saat digabung.

Untuk membedakan Continuous Delivery dari Continuous Deployment:

IstilahDeploy ke produksiContoh
Continuous IntegrationTidak — cukup build & test setiap perubahanmvn verify di tiap PR
Continuous DeliveryPerlu persetujuan manusiaPipeline menunggu tombol approve sebelum deploy
Continuous DeploymentOtomatis penuh, tanpa manusiaSetiap merge ke main langsung rilis ke produksi

Komponen Dasar GitHub Actions

Sebelum menulis pipeline, kita perlu mengenal lima "bahan baku" utama dari GitHub Actions. Semua konsep ini saling berkaitan, jadi mari kita lihat relasinya dulu.

 event (push) ──► workflow ──► job 1 (test) ──► steps
                              └► job 2 (deploy) ──► steps
                                   ▲
                                  runner (ubuntu-latest)
KomponenDefinisi singkatAnalogi sederhana
workflowFile YAML di .github/workflows/ yang mendefinisikan seluruh prosesResep masakan
eventPemicu yang membuat workflow berjalan (push, pull_request, schedule)Alarm pengingat
jobSatu unit kerja yang berjalan di satu mesinTugas di dapur
stepPerintah paling kecil di dalam jobLangkah dalam resep
runnerMesin virtual tempat job dieksekusiKompor
actionUnit reusable berisi langkah-langkah, bisa dibuat orang lainAlat masak siap pakai

Semua workflow disimpan dalam satu direktori khusus bernama .github/workflows/ di root repository Anda:

my-ecommerce/
├── .github/
│   └── workflows/
│       └── ci.yml            ← workflow CI kita
│       └── deploy.yml        ← workflow CD kita
├── src/
├── pom.xml
└── README.md

Struktur File Workflow

Sebuah workflow adalah file YAML. Jika Anda belum pernah melihat YAML sebelumnya, tidak perlu khawatir: YAML hanya format teks yang mengutamakan indentation (spasi) sebagai struktur, mirip cara Anda menulis catatan berurutan. Berikut contoh workflow CI lengkap untuk proyek Java Maven:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Run tests
        run: mvn -B clean verify

Mari kita bedah file di atas bagian per bagian. name hanyalah label untuk memudahkan Anda mengenali workflow di tab Actions. on: adalah bagian paling penting: ini mendefinisikan event yang memicu workflow. Pada contoh di atas, workflow berjalan ketika ada push ke branch main dan ketika ada pull_request (ke branch mana pun). jobs: berisi daftar pekerjaan. Setiap job dijalankan pada runs-on, yaitu mesin virtual yang Anda sewa untuk sementara — ubuntu-latest adalah pilihan paling umum. Terakhir, steps: berisi langkah-langkah berurutan:

  • uses: actions/checkout@v4action bawaan GitHub untuk menyalin kode repository ke runner. Tanpa langkah ini, runner tidak punya kode Anda sama sekali.
  • uses: actions/setup-java@v4 — memasang JDK versi 17 (distribusi temurin) dan, karena kita set cache: 'maven', otomatis menyimpan cache dependency Maven agar job berikutnya tidak perlu mengunduh internet dari nol.
  • run: mvn -B clean verify — menjalankan perintah Maven biasa di dalam shell runner.

Sintaks lengkap untuk on:, jobs:, steps:, dan kondisi if: dijelaskan secara resmi di dokumentasi workflow syntax for GitHub Actions. Dokumentasi itu akan Anda perlukan hampir setiap kali menulis pipeline baru.

Contoh Pipeline Java + Maven yang Lengkap

Untuk proyek nyata — misalnya backend e-commerce Spring Boot — pipeline CI biasanya lebih dari sekadar mvn verify. Berikut contohnya, lengkap dengan upload artifact dan build image Docker:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Build & test
        run: mvn -B clean verify

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

Apa gunanya langkah upload-artifact? Artifact adalah file yang dihasilkan oleh job (di sini: file JAR). Job lain di workflow yang sama — atau bahkan Anda sendiri — bisa mengunduhnya. Ini berguna jika job build dan job deploy kita pisahkan, atau jika kita ingin mengunduh JAR hasil test untuk diperiksa manual.

Menjalankan Workflow Hanya pada Kondisi Tertentu

Workflow yang berjalan di setiap push bisa menghabiskan waktu dan credit (kuota menit gratis GitHub) tanpa manfaat. Untungnya, GitHub Actions memberi beberapa kontrol yang presisi.

Batasi branch dengan menulis nama branch di bawah event:

on:
  push:
    branches: [main]

Batasi berdasarkan file yang berubah menggunakan paths — berguna jika Anda punya beberapa workflow, misalnya satu untuk aplikasi dan satu untuk infrastruktur:

on:
  pull_request:
    paths:
      - 'src/**'
      - 'pom.xml'

Filter di level job dengan if:. Kondisi ini dievaluasi terhadap context GitHub, dan nilai seperti github.ref memberi tahu kita branch mana yang sedang diproses:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Run tests
        run: mvn -B clean verify

  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy
        run: ./deploy.sh

Pada contoh di atas, job deploy hanya berjalan jika branch-nya main, dan hanya setelah job test sukses (lihat needs: test). Ini pola yang akan kita dalami lagi di bagian Environment & Deployment.

Secrets: Menyimpan Kredensial dengan Aman

Di hampir semua pipeline, Anda butuh nilai rahasia: password database, token registry Docker, SSH key server produksi. Aturan pertama dan terpenting: jangan pernah menulis nilai rahasia langsung di file workflow. File workflow biasanya hidup di repository publik — menaruh password di sana sama saja dengan membaginya ke seluruh dunia.

Secrets di GitHub Actions disimpan di Settings → Secrets and variables → Actions pada level repository. Anda bisa menambahkan secret seperti DOCKERHUB_TOKEN, lalu menggunakannya di workflow dengan sintaks ${{ secrets.NAMA_SECRET }}:

steps:
  - name: Login ke Docker Hub
    uses: docker/login-action@v3
    with:
      username: ${{ secrets.DOCKERHUB_USERNAME }}
      password: ${{ secrets.DOCKERHUB_TOKEN }}

Beberapa praktik aman yang harus selalu diingat — ini bagian dari panduan resmi security for GitHub Actions:

Praktik amanPenjelasan
Jangan hardcode secret di YAMLGunakan secrets.* yang diisi lewat Settings
Jangan log secretSekali tercetak di log, nilainya bisa disalahgunakan; gunakan ::add-mask:: atau jangan echo nilai secret
Batasi aksesSet permissions: minimum di workflow; jangan beri token akses berlebihan
Gunakan secret untuk token, bukan untuk biasaToken yang bisa dibatasi scope (misal packages:read) lebih aman daripada password akun
Audit GITHUB_TOKENToken otomatis GITHUB_TOKEN hanya boleh diizinkan sesuai kebutuhan job

GitHub sebenarnya punya mekanisme secret masking: nilai secret yang pernah dipakai otomatis disamarkan (***) di log. Tapi jangan mengandalkannya sepenuhnya — jika Anda sengaja mencetak secret dalam format yang sedikit diubah (misalnya dipecah per karakter), masking tidak akan mengenalinya.

Jangan pernah menjalankan echo ${{ secrets.X }} di workflow. "Hanya untuk tes" adalah kalimat paling berbahaya dalam CI. Sekali secret bocor ke log publik, seluruh akses itu harus dianggap kompromi.

Environment & Deployment

Untuk tahap deploy, GitHub Actions menyediakan konsep environment — sebuah pengelompokan deployment yang bisa memiliki protection rules. Yang paling berguna adalah required reviewers: sebelum job yang memakai environment itu boleh berjalan, orang tertentu harus menekan tombol approve terlebih dahulu.

name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'
      - name: Test
        run: mvn -B clean verify

  deploy-production:
    runs-on: ubuntu-latest
    needs: test
    environment: production
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'
      - name: Build Docker image
        run: |
          mvn -B clean package
          docker build -t ${{ secrets.REGISTRY }}/my-ecommerce:${{ github.sha }} .
      - name: Push image
        run: |
          echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
          docker push ${{ secrets.REGISTRY }}/my-ecommerce:${{ github.sha }}

Poin penting pada contoh di atas:

  • needs: test memastikan job deploy-production hanya dijalankan setelah job test selesai sukses. Inilah cara mengatur urutan antar job.
  • environment: production menautkan job ke environment yang bisa Anda konfigurasi proteksinya di Settings → Environments. Jika environment tersebut mewajibkan reviewers, deploy akan berhenti sampai ada persetujuan.
  • github.sha adalah hash commit saat ini. Menandai image dengan SHA membuat setiap image bisa dilacak balik ke commit yang menghasilkannya — ini sangat berguna untuk rollback.

Image yang sudah di-push ke registry bisa di-deploy dengan berbagai cara: SSH lalu docker pull + docker run di server, rolling update di Kubernetes, atau memakai action pihak ketiga. Contoh di atas cukup untuk memahami pola dasar: build → tag → push.

Untuk deployment Kubernetes yang sesungguhnya, lanjutkan ke Kubernetes Deployment — di artikel itu, image yang Anda push tadi akan dijemput oleh cluster untuk melakukan rolling update.

Strategi Branch: Trunk-based vs Feature Branch

Cara tim mengelola branch menentukan bagaimana pipeline Anda sebaiknya dirancang. Dua pendekatan yang umum:

StrategiCara kerjaKonsekuensi untuk pipeline
Trunk-basedSemua developer merge langsung ke branch utama (misal main), sering, dalam potongan kecilSemua push ke main harus lolos build & test; deploy bisa sangat sering
Feature branchPekerjaan besar dibuat di branch terpisah, di-merge lewat PR setelah direviewPipeline di PR cukup test saja; build + deploy hanya saat merge ke main

Banyak tim menggabungkan keduanya dengan pola yang kami pakai di seluruh artikel ini:

TriggerJalankan apaKenapa
push ke branch fiturTest sajaUmpan balik cepat, tanpa membuang resource
pull_requestTest + laporan coverageQuality gate sebelum kode masuk
push ke mainTest + build image + deployHanya kode yang lolos yang sampai ke produksi

Praktik Baik & Jebakan Umum

Setelah menyusun beberapa pipeline, ada beberapa pelajaran yang hampir selalu datang lewat pengalaman pahit. Mari kita bahas yang paling sering terjadi.

1. Workflow terlalu lambat karena mengunduh ulang dependency. Setiap job dimulai dari runner yang bersih; tanpa cache, Maven akan mengunduh seluruh dependency dari internet setiap kali. Solusinya: aktifkan cache: 'maven' di actions/setup-java atau gunakan action actions/cache untuk hal lain. Ini biasanya memangkas durasi pipeline hingga setengahnya.

2. Flaky test. Test yang kadang lolos kadang gagal tanpa perubahan kode adalah racun dalam CI — tim akan mulai mengabaikan tanda merah, dan bug sungguhan ikut tertutup. Periksa test yang bergantung pada timing, urutan eksekusi, atau state global. Idealnya, pipeline harus selalu hijau.

3. Job berjalan di setiap push tanpa batasan. Setiap menit runner gratis dihitung dari kuota bulanan. Batasi dengan branches: dan paths: sesuai kebutuhan, dan jangan jalankan build yang berat untuk perubahan dokumentasi.

4. Secret bocor lewat log. Sudah dibahas di atas, tapi ini layak diulang: tidak pernah ada alasan untuk echo nilai secret. Selalu gunakan ${{ secrets.X }} langsung sebagai argumen, dan audit log pipeline secara berkala.

5. Action yang tidak dipin ke versi spesifik. Menulis uses: actions/checkout@v4 memakai tag major — aman dan disarankan. Tapi untuk ketelitian maksimal di supply chain, Anda bisa mem-pin action ke SHA commit:

uses: actions/checkout@a5ac3e24009779c7dd35a9e1aef03d17d6c1c1f0

Keuntungannya: versi action tidak bisa berubah diam-diam. Kekurangannya: Anda harus meng-update SHA secara manual ketika ingin menaikkan versi.

6. pull_request_target yang disalahgunakan. Ini jebakan yang cukup serius untuk dipahami. Action/event pull_request_target menjalankan kode dari branch target (misal main) dengan akses ke secrets — tetapi kode yang di-checkout bisa saja mengandung perubahan jahat dari PR yang tidak direview. Panduan resmi security for GitHub Actions menyarankan: jangan gunakan pull_request_target untuk mengeksekusi kode yang berasal dari kontributor yang tidak dikenal. Jika Anda tidak yakin menggunakannya untuk apa, jangan gunakan sama sekali.

JebakanGejalaSolusi
Tanpa cache dependencyPipeline 5–10 menit di tiap runAktifkan cache Maven/Gradle
Flaky testPipeline kadang merah tanpa perubahanPerbaiki test, bukan retry
Workflow jalan di tiap pushKuota menit habis cepatFilter branches: & paths:
Secret di-logNilai rahasia tampil di log publikJangan echo secret, audit log
Action latest tanpa pinPerilaku berubah diam-diamGunakan tag major atau pin SHA
pull_request_targetRisk eksekusi kode tak dikenalHindari, atau pahami resikonya

Pipeline Nyata untuk Proyek E-Commerce

Sebagai penutup teknis, mari satukan semuanya dalam skenario nyata: backend e-commerce Java Spring Boot. Workflow CI (ci.yml) berjalan di setiap push dan pull_request, menjalankan mvn verify untuk memastikan semua test hijau:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Run tests
        run: mvn -B clean verify

Lalu workflow deploy (deploy.yml) yang hanya berjalan saat merge ke main, membangun image Docker, mengirimnya ke registry, dan memicu deployment — persis seperti pipeline yang dibahas di bagian Environment & Deployment. Dengan dua file YAML itu, Anda telah menyingkirkan dua sumber masalah paling umum di tim backend: "works on my machine" dan "lupa test sebelum deploy".

Ringkasan

CI/CD mengubah pekerjaan manual yang rawan lupa menjadi proses otomatis yang terdokumentasi rapi. GitHub Actions memungkinkan Anda melakukannya langsung dari repository: tulis workflow sebagai file YAML di .github/workflows/, picu lewat event seperti push dan pull_request, susun pekerjaan dalam jobs dan steps, serta jalankan di runner yang disediakan GitHub. Kunci dari pipeline yang baik adalah presisi: batasi kapan workflow berjalan dengan branches: dan paths:, urutkan job dengan needs:, jaga urutan test dulu, deploy kemudian, dan simpan semua kredensial sebagai secrets — bukan sebagai teks di file. Terakhir, ingat bahwa pipeline Anda adalah bagian dari produk: rawat dengan cache, hindari flaky test, dan perhatikan keamanan action yang Anda pakai.

Lanjut membaca

← Back to technical articles