GitHub Actions adalah platform CI/CD bawaan GitHub, terintegrasi langsung dengan repositori. Untuk proyek Kotlin open-source maupun privat di GitHub, Actions adalah pilihan default karena gratis untuk public repo (tidak terbatas menit) dan mudah dikonfigurasi dengan workflow YAML. Pada chapter ini kita akan membedah struktur workflow GitHub Actions untuk Kotlin, melihat pola umum seperti matrix build dan cache Gradle, dan menutup dengan aturan praktis agar workflow tetap cepat dan aman.
Struktur Workflow #
Workflow GitHub Actions didefinisikan dalam file YAML di .github/workflows/. Struktur dasarnya:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build
run: ./gradlew buildBagian on: mendefinisikan trigger — kapan workflow dijalankan. Bagian jobs: berisi satu atau lebih job yang berjalan paralel secara default; tiap job berjalan di runner terpisah (ubuntu-latest, windows-latest, atau macos-latest).
Workflow Standar Kotlin #
Cuplikan berikut mensimulasikan workflow standar Kotlin dengan sembilan langkah:
// github_actions.kt
// Mendemonstrasikan konsep GitHub Actions untuk Kotlin:
// - Struktur workflow YAML
// - Matrix build (multi-JDK)
// - Cache Gradle
// - Step build, test, dan upload artifact
//
// Kode ini mem-parsing dan menampilkan ringkasan workflow
// (mensimulasikan hasil yang akan dijalankan runner).
data class Langkah(
val nama: String,
val perintah: String,
val status: String
)
data class Workflow(
val nama: String,
val trigger: List<String>,
val jobs: Map<String, List<Langkah>>
) {
fun totalLangkah(): Int = jobs.values.sumOf { it.size }
}
fun main() {
println("=== GitHub Actions untuk Kotlin ===")
println()
println("--- 1. Trigger umum ---")
val trigger = listOf(
"push ke branch main / develop",
"pull_request ke main",
"schedule (cron) — nightly build",
"workflow_dispatch — manual trigger",
"release created — auto publish"
)
trigger.forEach { println(" - $it") }
println()
println("--- 2. Workflow standar Kotlin (ringkasan) ---")
val workflow = Workflow(
nama = "CI",
trigger = listOf("push", "pull_request"),
jobs = mapOf(
"build" to listOf(
Langkah("Checkout", "actions/checkout@v4", "OK"),
Langkah("Setup JDK 21", "actions/setup-java@v4", "OK"),
Langkah("Setup Kotlin", "use bundled kotlin 2.0.21", "OK"),
Langkah("Cache Gradle", "actions/cache@v4", "OK (cache hit)"),
Langkah("Compile", "./gradlew compileKotlin", "OK (12s)"),
Langkah("Lint (detekt)", "./gradlew detekt", "OK (5s)"),
Langkah("Unit Test", "./gradlew test", "OK (45s, 128 tests)"),
Langkah("Build fat JAR", "./gradlew shadowJar", "OK (8s, 42MB)"),
Langkah("Upload artifact", "actions/upload-artifact@v4", "OK")
)
)
)
println(" Nama workflow : ${workflow.nama}")
println(" Trigger : ${workflow.trigger.joinToString()}")
println(" Total langkah : ${workflow.totalLangkah()}")
workflow.jobs.forEach { (job, langkah) ->
println(" Job '$job':")
langkah.forEach { l ->
println(" ${l.status.padEnd(12)} ${l.nama.padEnd(22)} -> ${l.perintah}")
}
}
println()
println("--- 3. Matrix build (multi-JDK) ---")
val matrix = listOf(
Triple("ubuntu-latest", "17", "temurin"),
Triple("ubuntu-latest", "21", "temurin"),
Triple("windows-latest", "21", "temurin"),
Triple("macos-latest", "21", "temurin")
)
matrix.forEach { (os, jdk, dist) ->
println(" $os / JDK $jdk ($dist) -> OK")
}
println()
println("--- 4. Cache Gradle (key strategi) ---")
val cacheKey = listOf(
"primary : gradle-libs-\${{ runner.os }}-\${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}",
"restore-keys 1: gradle-libs-\${{ runner.os }}-",
"restore-keys 2: gradle-libs-"
)
cacheKey.forEach { println(" $it") }
println()
println("--- 5. Upload artifact ---")
val artifacts = listOf(
"fat-jar : build/libs/app-1.0.0-all.jar",
"test-report : build/reports/tests/test/",
"lint-report : build/reports/detekt/",
"junit-xml : build/test-results/test/"
)
artifacts.forEach { println(" $it") }
println()
println("--- 6. Contoh workflow YAML (ringkas) ---")
println(" name: CI")
println(" on: [push, pull_request]")
println(" jobs:")
println(" build:")
println(" runs-on: ubuntu-latest")
println(" strategy:")
println(" matrix:")
println(" jdk: [17, 21]")
println(" steps:")
println(" - uses: actions/checkout@v4")
println(" - uses: actions/setup-java@v4")
println(" with: { java-version: \${{ matrix.jdk }}, distribution: temurin }")
println(" - uses: actions/cache@v4")
println(" with: { path: ~/.gradle/caches, key: gradle-\${{ runner.os }}-\${{ hashFiles('**/*.gradle*') }} }")
println(" - run: ./gradlew build")
println()
println("--- 7. Aturan praktis GitHub Actions ---")
val aturan = listOf(
"1. Pin action ke versi SHA (bukan @v4) untuk audit",
"2. Pakai cache Gradle agar build berikutnya cepat",
"3. Pakai matrix build hanya bila perlu (memperlambat pipeline)",
"4. Pisahkan job lint, test, package untuk paralel",
"5. Upload artifact hanya bila seluruh job berhasil",
"6. Set timeout-minutes agar pipeline tidak hang",
"7. Pakai environment secrets untuk API token"
)
aturan.forEach { println(" $it") }
}Output yang Dihasilkan:
=== GitHub Actions untuk Kotlin ===
--- 1. Trigger umum ---
- push ke branch main / develop
- pull_request ke main
- schedule (cron) — nightly build
- workflow_dispatch — manual trigger
- release created — auto publish
--- 2. Workflow standar Kotlin (ringkasan) ---
Nama workflow : CI
Trigger : push, pull_request
Total langkah : 9
Job 'build':
OK Checkout -> actions/checkout@v4
OK Setup JDK 21 -> actions/setup-java@v4
OK Setup Kotlin -> use bundled kotlin 2.0.21
OK (cache hit) Cache Gradle -> actions/cache@v4
OK (12s) Compile -> ./gradlew compileKotlin
OK (5s) Lint (detekt) -> ./gradlew detekt
OK (45s, 128 tests) Unit Test -> ./gradlew test
OK (8s, 42MB) Build fat JAR -> ./gradlew shadowJar
OK Upload artifact -> actions/upload-artifact@v4
--- 3. Matrix build (multi-JDK) ---
ubuntu-latest / JDK 17 (temurin) -> OK
ubuntu-latest / JDK 21 (temurin) -> OK
windows-latest / JDK 21 (temurin) -> OK
macos-latest / JDK 21 (temurin) -> OK
--- 4. Cache Gradle (key strategi) ---
primary : gradle-libs-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys 1: gradle-libs-${{ runner.os }}-
restore-keys 2: gradle-libs-
--- 5. Upload artifact ---
fat-jar : build/libs/app-1.0.0-all.jar
test-report : build/reports/tests/test/
lint-report : build/reports/detekt/
junit-xml : build/test-results/test/
--- 6. Contoh workflow YAML (ringkas) ---
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
jdk: [17, 21]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: ${{ matrix.jdk }}, distribution: temurin }
- uses: actions/cache@v4
with: { path: ~/.gradle/caches, key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} }
- run: ./gradlew build
--- 7. Aturan praktis GitHub Actions ---
1. Pin action ke versi SHA (bukan @v4) untuk audit
2. Pakai cache Gradle agar build berikutnya cepat
3. Pakai matrix build hanya bila perlu (memperlambat pipeline)
4. Pisahkan job lint, test, package untuk paralel
5. Upload artifact hanya bila seluruh job berhasil
6. Set timeout-minutes agar pipeline tidak hang
7. Pakai environment secrets untuk API tokenPada cuplikan di atas, sembilan langkah dijalankan berurutan dalam satu job. Bila salah satu gagal, langkah berikutnya tidak dijalankan — kecuali bila langkah diberi continue-on-error: true. Hasil akhir berupa artifact (fat JAR) yang bisa diunduh dari halaman Actions GitHub.
Matrix Build #
Untuk pengujian multi-JDK dan multi-OS, gunakan matrix:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
jdk: [17, 21]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
distribution: 'temurin'
- run: ./gradlew testMatrix build akan menjalankan 6 job secara paralel (3 OS × 2 JDK). Trade-off: konsumsi menit Actions naik 6x. Untuk library publik, ini sepadan karena bug platform-spesifik tertangkap lebih awal.
Cache Gradle #
Build Gradle pertama bisa memakan 5-10 menit karena mengunduh semua dependensi. Dengan cache, build kedua dan seterusnya hanya butuh 30-60 detik. Kunci cache adalah hash dari file *.gradle* dan gradle-wrapper.properties:
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
gradle-key digunakan untuk menyimpan cache setelah build; restore-keys adalah fallback bila key utama tidak cocok. Strategi ini memastikan cache selalu hit selama file Gradle tidak banyak berubah.
Upload Artifact #
Setelah build berhasil, upload artefak agar bisa diunduh dari UI GitHub atau dipakai job berikutnya:
- uses: actions/upload-artifact@v4
with:
name: app-fat-jar
path: build/libs/*-all.jar
retention-days: 30Untuk container image, publish ke registry (Docker Hub, GHCR) lewat docker/build-push-action. Untuk library Kotlin, publish ke Maven Central lewat gradle publish (butuh GPG signing dan credentials Sonatype).
Trigger Lanjutan #
Selain push dan pull_request, Actions mendukung trigger lain:
schedule:— cron, untuk nightly build.workflow_dispatch:— manual trigger dari UI.release:— saat release dibuat di GitHub, auto-publish.repository_dispatch:— trigger dari API eksternal.
Untuk release publikasi library Kotlin, kombinasi push: tags: ['v*'] + workflow_dispatch: adalah pola umum — build otomatis saat tag v1.2.3 dibuat, dengan manual override bila perlu re-publish.
Aturan Praktis #
Beberapa aturan agar workflow tetap sehat:
- Pin action ke SHA — bukan
@v4, untuk keamanan rantai suplai. - Pakai cache Gradle — build kedua jadi 5-10x lebih cepat.
- Pisahkan job lint, test, package — paralel lebih cepat daripada serial.
- Set
timeout-minutes— pipeline tidak hang selamanya bila ada bug. - Pakai environment secrets — jangan hardcode token di workflow YAML.
- Upload artifact hanya bila build berhasil — gunakan
if: success().
Referensi Lanjutan #
- GitHub Actions — dokumentasi resmi GitHub Actions untuk workflow Kotlin CI/CD
Selanjutnya #
Pipeline hanya separuh cerita — kita juga harus memastikan aplikasi bisa dimatikan dengan aman saat container restart. Chapter berikutnya membahas graceful shutdown — menyelesaikan request in-flight dan menutup resource dengan benar.