CI/CD dengan GitHub Actions
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.
| Aspek | Manual (tanpa CI/CD) | Dengan CI/CD |
|---|---|---|
| Build | Dijalankan sendiri di laptop masing-masing | Otomatis di server (runner) setiap ada perubahan |
| Test | Sering dilupakan, hasilnya berbeda antar mesin | Selalu dijalankan, hasilnya konsisten dan terdokumentasi |
| Detect bug | Terlambat, sering baru ketahuan di produksi | Cepat, langsung saat kode di-push |
| Deploy | Copy-paste JAR via SSH, rawan salah langkah | Otomatis, terukur, bisa di-rollback |
| Dokumentasi | Tersebar di kepala orang dan chat | Tertulis 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:
| Istilah | Deploy ke produksi | Contoh |
|---|---|---|
| Continuous Integration | Tidak — cukup build & test setiap perubahan | mvn verify di tiap PR |
| Continuous Delivery | Perlu persetujuan manusia | Pipeline menunggu tombol approve sebelum deploy |
| Continuous Deployment | Otomatis penuh, tanpa manusia | Setiap 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)
| Komponen | Definisi singkat | Analogi sederhana |
|---|---|---|
workflow | File YAML di .github/workflows/ yang mendefinisikan seluruh proses | Resep masakan |
event | Pemicu yang membuat workflow berjalan (push, pull_request, schedule) | Alarm pengingat |
job | Satu unit kerja yang berjalan di satu mesin | Tugas di dapur |
step | Perintah paling kecil di dalam job | Langkah dalam resep |
runner | Mesin virtual tempat job dieksekusi | Kompor |
action | Unit reusable berisi langkah-langkah, bisa dibuat orang lain | Alat 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@v4— action 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 (distribusitemurin) dan, karena kita setcache: '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 kondisiif: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 aman | Penjelasan |
|---|---|
| Jangan hardcode secret di YAML | Gunakan secrets.* yang diisi lewat Settings |
| Jangan log secret | Sekali tercetak di log, nilainya bisa disalahgunakan; gunakan ::add-mask:: atau jangan echo nilai secret |
| Batasi akses | Set permissions: minimum di workflow; jangan beri token akses berlebihan |
| Gunakan secret untuk token, bukan untuk biasa | Token yang bisa dibatasi scope (misal packages:read) lebih aman daripada password akun |
Audit GITHUB_TOKEN | Token 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: testmemastikan jobdeploy-productionhanya dijalankan setelah jobtestselesai sukses. Inilah cara mengatur urutan antar job.environment: productionmenautkan job ke environment yang bisa Anda konfigurasi proteksinya di Settings → Environments. Jika environment tersebut mewajibkan reviewers, deploy akan berhenti sampai ada persetujuan.github.shaadalah 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:
| Strategi | Cara kerja | Konsekuensi untuk pipeline |
|---|---|---|
| Trunk-based | Semua developer merge langsung ke branch utama (misal main), sering, dalam potongan kecil | Semua push ke main harus lolos build & test; deploy bisa sangat sering |
| Feature branch | Pekerjaan besar dibuat di branch terpisah, di-merge lewat PR setelah direview | Pipeline 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:
| Trigger | Jalankan apa | Kenapa |
|---|---|---|
push ke branch fitur | Test saja | Umpan balik cepat, tanpa membuang resource |
pull_request | Test + laporan coverage | Quality gate sebelum kode masuk |
push ke main | Test + build image + deploy | Hanya 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.
| Jebakan | Gejala | Solusi |
|---|---|---|
| Tanpa cache dependency | Pipeline 5–10 menit di tiap run | Aktifkan cache Maven/Gradle |
| Flaky test | Pipeline kadang merah tanpa perubahan | Perbaiki test, bukan retry |
| Workflow jalan di tiap push | Kuota menit habis cepat | Filter branches: & paths: |
| Secret di-log | Nilai rahasia tampil di log publik | Jangan echo secret, audit log |
Action latest tanpa pin | Perilaku berubah diam-diam | Gunakan tag major atau pin SHA |
pull_request_target | Risk eksekusi kode tak dikenal | Hindari, 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
- Dokumentasi GitHub Actions
- Belajar konsep GitHub Actions
- Workflow syntax untuk GitHub Actions
- Keamanan GitHub Actions
- Dockerizing Java Applications — cara membuat image Java yang kecil dan aman, yang pipeline di atas gunakan untuk build & push
- Kubernetes Deployment — langkah berikutnya: bagaimana image yang Anda push ke registry di-deploy ke cluster secara otomatis