Antwa CodeAntwaCode Blog

Rekayasa7 menit baca

CI/CD Pipeline dengan GitHub Actions: Tutorial Lengkap

Tutorial CI/CD pipeline menggunakan GitHub Actions dari nol.

Baca dalam English

Pendahuluan

Halo teman-teman! 👋

Pernah nggak sih kalian merasa capek harus manual deploy setiap kali ada perubahan kode? Push code ke repository, lalu harus login ke server, pull code baru, jalankan migration, clear cache, restart service... dan seterusnya. Kalau hanya satu kali sih nggak masalah, tapi kalau harus dilakukan berulang-ulang setiap hari? Pasti melelahkan, kan?

Nah, di sinilah konsep CI/CD hadir sebagai solusi. Dan salah satu tools yang paling populer untuk mengimplementasikannya adalah GitHub Actions. Di tutorial kali ini, kita akan belajar bareng tentang apa itu CI/CD, kenapa kita butuh GitHub Actions, dan bagaimana cara membuat pipeline lengkap untuk proyek Laravel kita.

Siap? Let's go! 🚀

Apa Itu CI/CD?

Sebelum kita masuk ke implementasi, yuk pahami dulu konsep dasarnya.

Continuous Integration (CI)

Continuous Integration adalah praktik di mana setiap developer mengintegrasikan kode mereka ke dalam branch utama secara berkala (biasanya beberapa kali sehari). Setiap integrasi ini kemudian diverifikasi secara otomatis — mulai dari build, test, sampai linting.

Intinya, CI memastikan bahwa kode yang kita tulis selalu dalam kondisi sehat dan tidak merusak kode lain. Bayangkan kalau 5 orang developer bekerja di proyek yang sama tanpa CI — bisa berantakan banget, kan? 😅

Continuous Delivery / Continuous Deployment (CD)

Continuous Delivery memastikan bahwa kode yang sudah lolos CI selalu siap untuk dideploy ke production kapan pun. Artinya, ada pipeline otomatis yang mempersiapkan segalanya — tinggal kita klik tombol "deploy" saja.

Sedangkan Continuous Deployment adalah langkah lebih lanjut: setiap perubahan yang lolos CI akan langsung dideploy ke production secara otomatis, tanpa intervensi manusia.

Dalam praktiknya, banyak tim yang menerapkan Continuous Delivery (ada approval manual sebelum deploy) karena lebih aman. Tapi untuk project kecil atau side project, Continuous Deployment juga bisa jadi pilihan yang menarik.

Kenapa GitHub Actions?

Sebenarnya ada banyak tools CI/CD di luar sana seperti Jenkins, GitLab CI, CircleCI, Travis CI, dan lain-lain. Tapi GitHub Actions punya kelebihan tersendiri:

  1. Terintegrasi langsung dengan GitHub — Nggak perlu setup integrasi terpisah. Kalau repo kalian sudah di GitHub, tinggal pakai.
  2. Gratis untuk public repos — Untuk open source, GitHub Actions sangat generous dengan limitasi gratisnya.
  3. Marketplace yang kaya — Ada ribuan action siap pakai yang bisa kita gunakan, mulai dari deploy ke AWS sampai kirim notifikasi ke Slack.
  4. Mudah dipelajari — Syntax-nya berbasis YAML yang relatif mudah dipahami.
  5. Multi-platform — Bisa dijalankan di Ubuntu, Windows, dan macOS.

Perbandingan dengan Tools Lain

FeatureGitHub ActionsGitLab CIJenkinsCircleCI
Hosted Runner❌ (self-hosted)
Free TierGenerousModerateUnlimited (self-hosted)Limited
Marketplace20k+ actionsFewer1800+ pluginsOrbs
Setup ComplexityLowLowHighMedium

Konsep Dasar GitHub Actions

Sebelum kita membuat workflow, penting untuk memahami konsep-konsep dasar dalam GitHub Actions:

Repository

Workflow files disimpan di folder .github/workflows/ di dalam repository kita. Setiap file workflow ditulis dalam format YAML.

Workflow

Workflow adalah proses otomatis yang kita definisikan. Satu repository bisa memiliki banyak workflow, dan masing-masing workflow bisa dijalankan oleh trigger yang berbeda.

Event

Event adalah sesuatu yang memicu workflow untuk berjalan. Contohnya:

  • push — Ketika ada code yang di-push ke branch tertentu
  • pull_request — Ketika ada pull request yang dibuat atau di-update
  • schedule — Berdasarkan cron schedule
  • workflow_dispatch — Trigger manual dari UI GitHub

Job

Job adalah sekumpulan steps yang berjalan di runner yang sama. Multiple jobs bisa berjalan secara paralel atau berurutan (bergantung pada dependency).

Step

Step adalah instruksi individual dalam sebuah job. Step bisa menjalankan command, menggunakan action, atau menjalankan script.

Action

Action adalah unit reusable yang bisa kita gunakan untuk mempersingkat workflow. Contohnya, actions/checkout@v4 untuk checkout kode, atau actions/setup-php@v2 untuk setup PHP.

Runner

Runner adalah server yang menjalankan workflow. GitHub menyediakan hosted runners (Ubuntu, Windows, macOS) atau kita bisa menggunakan self-hosted runner.

Struktur Workflow Dasar

Oke, sekarang kita sudah paham konsep-konsep dasarnya. Yuk kita lihat struktur workflow GitHub Actions yang paling sederhana:

name: CI Pipeline
 
# Trigger: kapan workflow ini berjalan
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
 
# Jobs yang akan berjalan
jobs:
  # Nama job
  build:
    # Runner yang digunakan
    runs-on: ubuntu-latest
 
    # Steps dalam job ini
    steps:
      # Step 1: Checkout kode
      - name: Checkout code
        uses: actions/checkout@v4
 
      # Step 2: Jalankan command
      - name: Run a one-line script
        run: echo "Hello, GitHub Actions!"

Coba perhatikan strukturnya:

  1. name — Nama workflow yang akan tampil di tab Actions di GitHub
  2. on — Trigger yang memicu workflow
  3. jobs — Sekumpulan job yang akan dieksekusi
  4. steps — Langkah-langkah dalam setiap job

Setup Environment PHP untuk Laravel

Sebelum kita masuk ke contoh workflow Laravel yang lengkap, mari kita pahami cara setup environment PHP di GitHub Actions.

Menggunakan actions/setup-php

Action shivammathur/setup-php adalah action paling populer untuk setup PHP di GitHub Actions. Berikut cara menggunakannya:

steps:
  - name: Setup PHP
    uses: shivammathur/setup-php@v2
    with:
      php-version: '8.2'
      extensions: mbstring, xml, ctype, json, bcmath, pdo, sqlite3
      tools: composer:v2
      coverage: xdebug

Parameter extensions memungkinkan kita menginstal PHP extension yang dibutuhkan, dan tools memungkinkan kita menginstal tools tambahan seperti Composer.

Menggunakan Service Containers

Untuk Laravel, kita biasanya butuh database saat testing. GitHub Actions menyediakan service containers yang bisa kita gunakan:

services:
  mysql:
    image: mysql:8.0
    env:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: testing
    ports:
      - 3306:3306
    options: >-
      --health-cmd="mysqladmin ping"
      --health-interval=10s
      --health-timeout=5s
      --health-retries=3

Service container akan berjalan bersama job kita dan otomatis dihentikan setelah job selesai.

Contoh Workflow Lengkap untuk Laravel

Sekarang kita akan membuat workflow CI/CD lengkap untuk proyek Laravel. Workflow ini akan melakukan:

  1. Test — Menjalankan PHPUnit test
  2. Lint — Menjalankan PHP CodeSniffer
  3. Build — Build assets dengan Vite
  4. Deploy — Deploy ke production

File: .github/workflows/ci.yml

name: CI/CD Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  PHP_VERSION: '8.2'
  NODE_VERSION: '20'
 
jobs:
  # ============================================
  # Job 1: Code Quality & Testing
  # ============================================
  test:
    name: Test
    runs-on: ubuntu-latest
 
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          extensions: mbstring, xml, ctype, json, bcmath, pdo, sqlite3
          tools: composer:v2
          coverage: xdebug
 
      - name: Get Composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
 
      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-
 
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress --no-suggest
 
      - name: Copy environment file
        run: cp .env.example .env
 
      - name: Generate application key
        run: php artisan key:generate
 
      - name: Configure environment
        run: |
          sed -i 's/DB_DATABASE=laravel/DB_DATABASE=testing/' .env
          sed -i 's/DB_USERNAME=root/DB_USERNAME=root/' .env
          sed -i 's/DB_PASSWORD=/DB_PASSWORD=password/' .env
 
      - name: Run migrations
        run: php artisan migrate --force
        env:
          DB_CONNECTION: mysql
 
      - name: Run tests
        run: php artisan test --coverage --min=80
        env:
          DB_CONNECTION: mysql
 
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml
          fail_ci_if_error: false
 
  # ============================================
  # Job 2: Code Linting
  # ============================================
  lint:
    name: Lint
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          tools: composer:v2, phpcs
 
      - name: Get Composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
 
      - name: Cache Composer dependencies
        uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-
 
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress --no-suggest
 
      - name: Run PHP CodeSniffer
        run: vendor/bin/phpcs --standard=PSR12 --report=checkstyle app/ || true
 
      - name: Run PHPStan (static analysis)
        vendor/bin/phpstan analyse --memory-limit=2G
 
  # ============================================
  # Job 3: Build Assets
  # ============================================
  build:
    name: Build Assets
    runs-on: ubuntu-latest
    needs: [test, lint]
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
 
      - name: Install npm dependencies
        run: npm ci
 
      - name: Build production assets
        run: npm run build
        env:
          VITE_APP_URL: ${{ secrets.APP_URL }}
 
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-assets
          path: public/build/
          retention-days: 7
 
  # ============================================
  # Job 4: Deploy to Production
  # ============================================
  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [build]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-assets
          path: public/build/
 
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          tools: composer:v2
 
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            composer install --prefer-dist --no-dev --optimize-autoloader
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
            php artisan queue:restart
 
      - name: Deploy artifacts to server
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          source: "public/build/"
          target: "/var/www/myapp/public/build/"

Menjelaskan Workflow di Atas

Mari kita bedah satu per satu bagian dari workflow di atas:

Job: Test

Job ini berfokus pada menjalankan PHPUnit test. Perhatikan beberapa hal penting:

  1. Service MySQL — Kita menggunakan service container MySQL untuk testing, karena Laravel biasanya membutuhkan database saat menjalankan test.
  2. Composer Cache — Kita caching Composer dependencies untuk mempercepat build time di run berikutnya.
  3. Environment Setup — Kita copy .env.example ke .env, generate application key, dan konfigurasi database.
  4. Coverage Report — Kita menggunakan --coverage --min=80 untuk memastikan minimal 80% kode ter-cover oleh test.

Job: Lint

Job ini menjalankan code quality checks:

  1. PHP CodeSniffer — Memastikan kode mengikuti coding standard PSR-12.
  2. PHPStan — Melakukan static analysis untuk menemukan potensi bug sebelum code dijalankan.

Job: Build

Job ini build assets frontend (CSS, JS) menggunakan Vite:

  1. Node.js Setup — Kita setup Node.js untuk menjalankan build command.
  2. npm ci — Menginstal dependencies berdasarkan package-lock.json untuk reproducibility.
  3. Upload Artifacts — Build artifacts disimpan agar bisa digunakan oleh job deploy.

Job: Deploy

Job ini hanya berjalan di branch main dan hanya saat event push:

  1. Conditional Executionif: github.ref == 'refs/heads/main' memastikan deploy hanya terjadi di branch main.
  2. Environment Protectionenvironment: production memungkinkan kita mengatur approval rules di GitHub.
  3. SSH Deploy — Kita menggunakan appleboy/ssh-action untuk menjalankan deploy command di server.

Secrets Management

Salah satu hal penting dalam CI/CD adalah bagaimana kita mengelola secrets (kata sandi, API keys, SSH keys, dll). GitHub Actions menyediakan fitur Repository Secrets yang aman.

Menambahkan Secrets

  1. Buka repository di GitHub
  2. Masuk ke SettingsSecrets and variablesActions
  3. Klik New repository secret
  4. Masukkan nama dan nilai secret

Menggunakan Secrets dalam Workflow

steps:
  - name: Deploy
    env:
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
      API_KEY: ${{ secrets.API_KEY }}
      APP_URL: ${{ secrets.APP_URL }}
    run: |
      echo "Deploying to $APP_URL"
      # Jalankan deploy script

Tips Keamanan

  1. Jangan pernah hardcode secrets di dalam file workflow. Selalu gunakan repository secrets.
  2. Gunakan Environment Secrets untuk secrets yang berbeda per environment (staging vs production).
  3. Rotate secrets secara berkala — ubah password atau API key secara berkala untuk keamanan.
  4. Gunakan OpenID Connect untuk authenticasi ke cloud providers (AWS, GCP, Azure) tanpa menyimpan credentials.

OpenID Connect untuk AWS

permissions:
  id-token: write
  contents: read
 
steps:
  - name: Configure AWS credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
      aws-region: ap-southeast-1
 
  - name: Deploy to S3
    run: aws s3 sync ./public/build/ s3://my-bucket/build/

Caching untuk Mempercepat Build

Build time yang lama bisa sangat mengganggu produktivitas. GitHub Actions menyediakan beberapa cara untuk caching:

Cache Composer Dependencies

- name: Get Composer cache directory
  id: composer-cache
  run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
 
- name: Cache Composer dependencies
  uses: actions/cache@v4
  with:
    path: ${{ steps.composer-cache.outputs.dir }}
    key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
    restore-keys: |
      ${{ runner.os }}-composer-

restore-keys digunakan sebagai fallback jika exact match cache tidak ditemukan. Ini memastikan kita selalu mendapat cache terdekat yang tersedia.

Cache Node.js Dependencies

- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'

Action setup-node sudah built-in support untuk caching npm dependencies. Cukup tambahkan cache: 'npm' dan otomatis semua dependencies akan di-cache.

Cache Docker Layers

- name: Build Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

Dengan caching Docker layers, build time bisa berkurang drastis dari beberapa menit menjadi puluhan detik.

Matrix Builds

Matrix builds memungkinkan kita menjalankan workflow dengan berbagai kombinasi versi PHP, Node.js, atau database secara paralel. Ini sangat berguna untuk memastikan kompatibilitas lintas versi.

Contoh: Test di Berbagai Versi PHP

jobs:
  test:
    name: PHP ${{ matrix.php-version }}
    runs-on: ubuntu-latest
 
    strategy:
      fail-fast: false
      matrix:
        php-version: ['8.1', '8.2', '8.3']
        database: ['mysql', 'sqlite']
 
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
        # Hanya jalankan MySQL jika matrix database = mysql
        if: ${{ matrix.database == 'mysql' }}
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-version }}
          extensions: mbstring, xml, ctype, json, bcmath, pdo
          tools: composer:v2
 
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress
 
      - name: Setup SQLite for testing
        if: ${{ matrix.database == 'sqlite' }}
        run: |
          touch database/database.sqlite
          cp .env.example .env
          sed -i 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' .env
          sed -i 's/DB_DATABASE=laravel/DB_DATABASE=database\/database.sqlite/' .env
 
      - name: Setup MySQL for testing
        if: ${{ matrix.database == 'mysql' }}
        run: |
          cp .env.example .env
          sed -i 's/DB_DATABASE=laravel/DB_DATABASE=testing/' .env
          sed -i 's/DB_USERNAME=root/DB_USERNAME=root/' .env
          sed -i 's/DB_PASSWORD=/DB_PASSWORD=password/' .env
 
      - name: Generate application key
        run: php artisan key:generate
 
      - name: Run migrations
        run: php artisan migrate --force
 
      - name: Run tests
        run: php artisan test

Dengan konfigurasi di atas, kita akan menjalankan test di 6 kombinasi:

  • PHP 8.1 + MySQL
  • PHP 8.1 + SQLite
  • PHP 8.2 + MySQL
  • PHP 8.2 + SQLite
  • PHP 8.3 + MySQL
  • PHP 8.3 + SQLite

Contoh: Matrix Build untuk Multi-Platform

jobs:
  build:
    name: Build (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
 
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
 
      - name: Install dependencies
        run: npm ci
 
      - name: Run tests
        run: npm test
 
      - name: Build
        run: npm run build

Workflow untuk Deploy ke Berbagai Platform

Deploy ke Vercel

name: Deploy to Vercel
 
on:
  push:
    branches: [main]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'
          working-directory: ./

Deploy ke Docker Hub

name: Build and Push Docker Image
 
on:
  push:
    tags:
      - 'v*'
 
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        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_TOKEN }}
 
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: myusername/myapp
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha
 
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Deploy ke AWS ECS

name: Deploy to AWS ECS
 
on:
  push:
    branches: [main]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
 
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-southeast-1
 
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2
 
      - name: Build, tag, and push image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: myapp
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
 
      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ecs-task-definition.json
          service: myapp-service
          cluster: myapp-cluster
          wait-for-service-stability: true

Reusable Workflows

Ketika kita memiliki banyak proyek dengan workflow yang mirip, reusable workflows bisa menghemat waktu dan menjaga konsistensi.

Membuat Reusable Workflow

# .github/workflows/reusable-laravel-ci.yml
name: Reusable Laravel CI
 
on:
  workflow_call:
    inputs:
      php-version:
        description: 'PHP version'
        required: false
        default: '8.2'
        type: string
      node-version:
        description: 'Node.js version'
        required: false
        default: '20'
        type: string
    secrets:
      SERVER_HOST:
        required: true
      SERVER_USER:
        required: true
      SERVER_SSH_KEY:
        required: true
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ inputs.php-version }}
      - run: composer install --prefer-dist --no-progress
      - run: php artisan test
 
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ inputs.php-version }}
          tools: phpcs
      - run: composer install --prefer-dist --no-progress
      - run: vendor/bin/phpcs --standard=PSR12 app/

Menggunakan Reusable Workflow

# .github/workflows/ci.yml (di proyek utama)
name: CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  ci:
    uses: ./.github/workflows/reusable-laravel-ci.yml
    with:
      php-version: '8.2'
    secrets: inherit

Tips dan Best Practices

1. Gunakan concurrency untuk menghemat resources

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Ini akan membatalkan workflow yang sedang berjalan di branch yang sama ketika ada push baru.

2. Gunakan environments untuk approval gates

jobs:
  deploy:
    environment: 
      name: production
      url: https://myapp.com

Anda bisa mengatur approval rules di Settings → Environments → production.

3. Gunakan dependabot untuk update actions

Buat file .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

4. Pantau workflow runs

GitHub Actions menyediakan dashboard untuk memantau semua workflow runs. Perhatikan:

  • Duration — Jika workflow mulai melambati, mungkin ada masalah dengan caching.
  • Failure rate — Jika banyak gagal, periksa apakah ada flaky tests.
  • Cost — Pantau penggunaan minutes, terutama untuk private repos.

5. Jangan lupa untuk test workflow Anda

Sebelum push ke main, test workflow di branch feature terlebih dahulu. Buat branch baru, buat perubahan di workflow file, dan push. Pastikan workflow berjalan dengan benar sebelum merge ke main.

Troubleshooting Umum

Workflow gagal di step "composer install"

Pastikan versi PHP yang digunakan kompatibel dengan dependencies di composer.json. Gunakan matrix builds untuk test di berbagai versi PHP.

Test gagal karena database

Pastikan service container MySQL sudah berjalan sebelum migrasi dijalankan. Gunakan health check untuk memastikan MySQL siap menerima koneksi.

Deploy gagal dengan SSH error

Pastikan SSH key yang digunakan sudah benar dan terdaftar di server. Juga, pastikan server IP sudah ditambahkan ke allowlist di firewall.

Workflow lambat

  1. Gunakan caching untuk Composer dan npm dependencies
  2. Gunakan npm ci alih-alih npm install
  3. Pertimbangkan untuk menggunakan self-hosted runner untuk build yang berat

Kesimpulan

CI/CD dengan GitHub Actions adalah cara yang powerful untuk mengotomasi pipeline development kita. Dengan memahami konsep dasar workflow, job, steps, dan action, kita bisa membuat pipeline yang robust dan reliable.

Beberapa poin penting yang perlu diingat:

  1. CI/CD bukan sekedar tools — ini adalah mindset dan praktik kerja yang membantu tim bekerja lebih efisient.
  2. GitHub Actions sangat fleksibel — bisa digunakan untuk hampir semua kebutuhan otomasi, bukan hanya CI/CD.
  3. Mulai dari yang sederhana — tidak perlu langsung membuat workflow yang kompleks. Mulai dari test saja, lalu tambah step satu per satu.
  4. Manfaatkan marketplace — banyak action yang sudah tersedia dan bisa langsung digunakan.
  5. Jaga keamanan — selalu gunakan secrets management dan hindari hardcode credentials.

Sekarang giliran kalian untuk mencoba! Buat workflow pertama kalian, dan rasakan bagaimana rasanya push code dan lihat semuanya berjalan otomatis. Happy coding! 🎉


Artikel ini ditulis sebagai bagian dari seri tutorial DevOps. Jika kalian punya pertanyaan atau ingin request topik lain, jangan ragu untuk meninggalkan komentar di bawah!

Tulisan lain