1. Pengenalan CI/CD
CI/CD (Continuous Integration / Continuous Deployment) adalah praktik DevOps yang mengotomatiskan proses build, test, dan deployment aplikasi. GitHub Actions adalah platform CI/CD yang terintegrasi langsung dengan GitHub, memungkinkan Anda membuat workflow otomatis tanpa memerlukan tool eksternal.
Diperkenalkan pada November 2019, GitHub Actions dengan cepat menjadi salah satu platform CI/CD paling populer karena integrasinya yang mulus dengan repository GitHub, marketplace actions yang luas, dan kemudahan konfigurasi menggunakan YAML.
Konsep Dasar CI/CD
| Konsep | Penjelasan |
|---|---|
| Continuous Integration (CI) | Developer sering merge kode ke branch utama. Setiap merge otomatis menjalankan build dan test untuk mendeteksi error lebih awal |
| Continuous Delivery (CD) | Setiap perubahan yang lolos CI otomatis di-deploy ke staging environment. Deployment ke production masih butuh approval manual |
| Continuous Deployment (CD) | Seperti Continuous Delivery, tapi deployment ke production juga otomatis tanpa approval manual |
Mengapa GitHub Actions?
| Keunggulan | Penjelasan |
|---|---|
| Terintegrasi GitHub | Tidak perlu tool terpisah β langsung di repository GitHub Anda |
| Gratis untuk Public Repo | 2000 menit/bulan untuk private repo, unlimited untuk public |
| Marketplace Luas | Ribuan pre-built actions siap pakai dari komunitas |
| Multi-OS | Runner tersedia untuk Ubuntu, Windows, macOS, dan ARM |
| Konfigurasi YAML | Workflow didefinisikan dalam YAML β mudah di-version control |
| Matrix Builds | Jalankan test di beberapa versi OS/bahasa secara paralel |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β CI/CD PIPELINE WITH GITHUB ACTIONS β β β β Developer β β ββββββββββββ β β β git push βββββ Trigger βββββΊββββββββββββββββββββββββββββββββ β β ββββββββββββ β GitHub Actions Workflow β β β β β β β β ββββββββββ βββββββββββββ β β β β β BUILD ββββΊβ TEST β β β β β β Code β β Unit + β β β β β β β β E2E β β β β β ββββββββββ βββββββ¬ββββββ β β β β β β β β β ββββββββββββ β β β βββββββββββββββββ β βΌ β β β β PRODUCTION βββββββββ βββββββββββββ β β β β (Cloud) βDeploy β β DEPLOY β β β β βββββββββββββββββ β βββββββββββββ β β β β β β β β β Pass β Deploy β β β β β Fail β Notify β β β ββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Workflow Syntax
Workflow GitHub Actions didefinisikan dalam file YAML yang disimpan di direktori .github/workflows/. Setiap file YAML merepresentasikan satu workflow yang bisa dipicu oleh berbagai events di repository.
Struktur File Workflow
# .github/workflows/ci.yml
# CI Pipeline dengan GitHub Actions
name: CI Pipeline # Nama workflow (tampil di tab Actions)
# Trigger workflow
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# Environment variables (aksesible di semua jobs)
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
# Jobs yang akan dijalankan
jobs:
lint-and-test:
name: Lint & Test
runs-on: ubuntu-latest # Runner OS
steps:
# 1. Checkout kode dari repository
- name: Checkout code
uses: actions/checkout@v4
# 2. Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
# 3. Install dependencies
- name: Install dependencies
run: npm ci
# 4. Jalankan linter
- name: Run ESLint
run: npm run lint
# 5. Jalankan unit tests
- name: Run tests
run: npm test -- --coverage
env:
CI: true
# 6. Upload coverage report
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
Komponen Utama Workflow
| Komponen | Penjelasan |
|---|---|
name | Nama workflow β tampil di GitHub UI |
on | Event trigger β kapan workflow dijalankan |
env | Environment variables global |
jobs | Kumpulan jobs yang dijalankan (paralel atau sequential) |
steps | Langkah-langkah dalam satu job |
uses | Menjalankan pre-built action dari marketplace |
run | Menjalankan shell command |
with | Input parameter untuk action |
needs | Dependency antar jobs |
Gunakan act (https://github.com/nektos/act) untuk menjalankan workflow GitHub Actions secara lokal saat development. Ini menghemat waktu karena tidak perlu push ke GitHub setiap kali ingin test workflow.
3. Jobs dan Steps
Jobs adalah unit dasar eksekusi dalam GitHub Actions. Setiap job berjalan di environment runner yang terpisah (VM terpisah). Secara default, jobs berjalan secara paralel, tetapi Anda bisa membuatnya sequential dengan needs.
Steps adalah langkah-langkah individual dalam satu job. Steps berjalan secara sequential β step berikutnya hanya dijalankan setelah step sebelumnya berhasil.
Contoh Multi-Job dengan Dependencies
name: Full CI/CD Pipeline
on:
push:
branches: [main]
jobs:
# === JOB 1: Test (berjalan dulu) ===
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Run linting
run: flake8 src/ --max-line-length=100
- name: Run unit tests
run: pytest tests/ -v --cov=src --cov-report=xml
- name: Upload test coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.xml
# === JOB 2: Build (butuh test selesai) ===
build:
name: Build Docker Image
runs-on: ubuntu-latest
needs: test # Hanya jalan setelah test selesai
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myuser/myapp:latest
myuser/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# === JOB 3: Deploy (butuh build selesai) ===
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build # Hanya jalan setelah build selesai
if: github.ref == 'refs/heads/main' # Hanya di main branch
environment: production # Butuh approval manual
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d
docker system prune -f
βββββββββββ βββββββββββ ββββββββββββββ β test ββββββΊβ build ββββββΊβ deploy β β βneedsβ βneedsβ β β lint β β docker β β ssh deploy β β unit β β push β β β β coverageβ β β β β βββββββββββ βββββββββββ ββββββββββββββ Parallel within job: Sequential between jobs: ββββββββββββββββββββ test β build β deploy β lint ββΊ test β β coverage upload β ββββββββββββββββββββ
Setiap job berjalan di VM terpisah, sehingga file yang dihasilkan di satu job tidak tersedia di job lain. Gunakan actions/upload-artifact dan actions/download-artifact untuk berbagi file antar jobs.
4. Triggers: Events yang Memicu Workflow
GitHub Actions bisa dipicu oleh berbagai events di repository maupun secara manual. Kunci on di workflow YAML mendefinisikan kapan workflow akan dijalankan.
Berbagai Jenis Trigger
# === PUSH & PULL REQUEST ===
on:
push:
branches: [main, develop, 'release/**']
paths: ['src/**', 'tests/**'] # Hanya jika file ini berubah
paths-ignore: ['docs/**', '*.md']
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# === SCHEDULE (CRON) ===
on:
schedule:
# Setiap hari jam 2 pagi UTC (cron syntax)
- cron: '0 2 * * *'
# Setiap Senin jam 9 pagi UTC
- cron: '0 9 * * 1'
# === WORKFLOW DISPATCH (Manual) ===
on:
workflow_dispatch:
inputs:
environment:
description: 'Deploy target'
required: true
default: 'staging'
type: choice
options:
- staging
- production
version:
description: 'Version tag'
required: false
# === REPOSITORY DISPATCH ===
on:
repository_dispatch:
types: [deploy-command]
# === MULTIPLE TRIGGERS ===
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 0' # Setiap Minggu
workflow_dispatch:
# === RELEASE ===
on:
release:
types: [published, edited]
# === ISSUE & COMMENTS ===
on:
issues:
types: [opened, edited, labeled]
issue_comment:
types: [created]
Filter dengan Paths dan Tags
# Trigger hanya saat file di folder tertentu berubah
on:
push:
paths:
- 'src/**'
- 'package*.json'
- '!src/**/*.test.js' # Kecuali file test
# Trigger saat tag di-push (untuk release)
on:
push:
tags:
- 'v*' # Cocokkan v1.0, v2.3.1, dll
# Filter berdasarkan file yang diubah
on:
pull_request:
paths:
- 'docs/**'
- 'api/**'
5. Mengelola Secrets
GitHub Secrets memungkinkan Anda menyimpan data sensitif seperti API keys, password, dan tokens secara aman. Secrets tidak akan tampil di log workflow dan tidak bisa diakses oleh fork repository.
Menambahkan Secrets
- Buka repository di GitHub
- Klik Settings β Secrets and variables β Actions
- Klik New repository secret
- Masukkan nama (misal:
DOCKER_PASSWORD) dan value
Menggunakan Secrets di Workflow
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Menggunakan secret sebagai environment variable
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
echo "Deploying..."
aws s3 sync ./dist s3://my-bucket
# Menggunakan secret dengan action
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
# Menggunakan secret untuk SSH
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/app && git pull && docker compose up -d
# === ENVIRONMENT SECRETS ===
# Secrets bisa dibatasi per environment
# (Settings β Environments β pilih environment β Add secret)
jobs:
deploy-prod:
runs-on: ubuntu-latest
environment: production # Hanya akses secrets di environment ini
steps:
- run: echo "Deploying to ${{ secrets.DEPLOY_URL }}"
Jangan pernah mencetak secrets ke log! Jangan gunakan echo ${{ secrets.MY_SECRET }} β meskipun GitHub akan otomatis menyensor output, lebih baik hindari sama sekali. Gunakan secrets hanya di environment variables atau input action.
6. Caching Dependencies
Setiap kali workflow berjalan, runner memulai dengan environment yang bersih β semua dependencies harus di-download ulang. Caching memungkinkan Anda menyimpan dependencies agar tidak perlu di-download setiap kali, mempercepat workflow secara signifikan.
Cara Cache dengan actions/cache
# === METHOD 1: Built-in cache di setup actions ===
# Node.js
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Otomatis cache node_modules
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip' # Otomatis cache pip packages
# === METHOD 2: actions/cache (lebih fleksibel) ===
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# === METHOD 3: Docker layer caching ===
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
# === METHOD 4: Cache Gradle (Java) ===
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'
Strategi Cache Key
| Strategi | Key Pattern | Kapan Cache Miss |
|---|---|---|
| Lockfile hash | ${{ hashFiles('**/package-lock.json') }} | Saat dependencies berubah |
| Date-based | ${{ runner.os }}-${{ github.run_id }} | Setiap run (tidak efektif) |
| Branch-specific | ${{ runner.os }}-${{ github.ref }} | Per branch |
| Fallback | Gunakan restore-keys | Cache parsial jika miss |
7. Deploy ke Cloud
Salah satu kekuatan utama GitHub Actions adalah kemampuan deploy ke berbagai cloud provider secara otomatis. Berikut contoh deployment ke beberapa platform populer:
Deploy ke AWS (S3 + CloudFront)
name: Deploy Frontend to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Build
run: |
npm ci
npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-southeast-1
- name: Deploy to S3
run: |
aws s3 sync ./dist s3://my-frontend-bucket \
--delete \
--cache-control "max-age=31536000"
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_ID }} \
--paths "/*"
Deploy ke VPS dengan SSH
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
set -e
cd /opt/myapp
# Pull kode terbaru
git fetch origin main
git reset --hard origin/main
# Build dan restart
docker compose build --no-cache
docker compose down
docker compose up -d
# Cleanup
docker system prune -f
echo "β
Deploy berhasil!"
Deploy ke Docker Hub + Server
name: Build & Push Docker Image
on:
push:
tags: ['v*'] # Hanya saat tag version di-push
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
myuser/myapp:latest
myuser/myapp:${{ steps.version.outputs.VERSION }}
cache-from: type=gha
cache-to: type=gha,mode=max
8. Matrix Builds
Matrix builds memungkinkan Anda menjalankan job yang sama dengan beberapa kombinasi parameter (misalnya: berbagai versi Node.js, OS, atau database). Ini sangat berguna untuk memastikan kompatibilitas lintas platform.
Contoh Matrix Build
name: Test Matrix
on: [push, pull_request]
jobs:
test:
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
exclude:
# Kecualikan kombinasi tertentu
- node-version: 18
os: windows-latest
include:
# Tambahkan kombinasi ekstra
- node-version: 20
os: macos-latest
experimental: true
fail-fast: false # Jangan stop jika satu matrix gagal
continue-on-error: ${{ matrix.experimental || false }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
# Upload coverage hanya untuk satu kombinasi
- name: Upload coverage
if: matrix.node-version == 20 && matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
Contoh Matrix dengan Database
jobs:
test:
name: Test with ${{ matrix.db }}
runs-on: ubuntu-latest
strategy:
matrix:
db: [postgres, mysql]
services:
# Service container otomatis dijalankan
postgres:
image: postgres:16
if: matrix.db == 'postgres'
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
mysql:
image: mysql:8
if: matrix.db == 'mysql'
env:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: pytest tests/ -v
env:
DATABASE_TYPE: ${{ matrix.db }}
9. Best Practices GitHub Actions
Mengikuti best practices saat menggunakan GitHub Actions sangat penting untuk keamanan, performa, dan maintainability pipeline CI/CD Anda.
Keamanan
| Praktik | Alasan |
|---|---|
| Pin action versions | Gunakan SHA commit, bukan tag β uses: actions/checkout@abc123 |
| Least privilege | Berikan permissions hanya yang dibutuhkan: permissions: contents: read |
| Audit third-party actions | Review kode source sebelum menggunakan action dari marketplace |
| Environment secrets | Gunakan environment-level secrets untuk production credentials |
| Tidak print secrets | Jangan pernah echo atau print nilai secrets |
Performa
# 1. Gunakan permissions minimal
permissions:
contents: read
packages: write
# 2. Set timeout untuk mencegah job berjalan terlalu lama
jobs:
test:
timeout-minutes: 15 # Maksimal 15 menit
# 3. Gunakan concurrency untuk cancel run lama
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# 4. Pisahkan workflow untuk CI dan CD
# .github/workflows/ci.yml β Jalankan di setiap push/PR
# .github/workflows/deploy.yml β Hanya di merge ke main
# 5. Gunakan reusable workflows
# .github/workflows/reusable-deploy.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
# 6. Conditional steps untuk efisiensi
steps:
- name: Deploy only on main
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: ./deploy.sh
Gunakan concurrency untuk membatalkan workflow yang sudah tidak relevan β misalnya saat Anda push commit baru ke PR yang sama, workflow dari commit sebelumnya otomatis dibatalkan. Ini menghemat menit GitHub Actions.
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang CI/CD dengan GitHub Actions: