Pendahuluan
Setiap kali kamu deploy aplikasi Laravel, apakah kamu:
- SSH ke VPS
git pull origin maincomposer installphp artisan migratephp artisan optimize- Restart queue worker…
Proses manual ini melelahkan, lambat, dan rawan kesalahan manusia. Lupa satu langkah? Bug di production.
CI/CD (Continuous Integration / Continuous Deployment) mengotomatiskan seluruh proses ini. Setiap kali kamu push ke branch main:
Push to main
│
▼
GitHub Actions: Jalankan semua test
│ (jika test gagal → STOP, tidak deploy)
▼
GitHub Actions: SSH ke VPS
│
▼
Jalankan deploy.sh otomatis
│
▼
Kirim notifikasi: "Deploy berhasil ✅"
Tidak perlu SSH manual. Tidak ada langkah yang terlupa.
Apa itu CI/CD?
Continuous Integration (CI): Setiap kode yang di-push otomatis diuji. Jika test gagal, developer langsung tahu sebelum kode masuk production.
Continuous Deployment (CD): Setelah CI berhasil, kode otomatis di-deploy ke server.
Manfaat:
- Tidak ada “works on my machine” — test berjalan di environment bersih
- Cepat tahu jika ada yang rusak — dalam 2-3 menit setelah push
- Deploy lebih sering, lebih aman — bukan deploy besar sekali sebulan yang menakutkan
Langkah 1: Buat Workflow File
GitHub Actions membaca file workflow dari .github/workflows/:
mkdir -p .github/workflows
touch .github/workflows/laravel.yml
Struktur dasar workflow:
# .github/workflows/laravel.yml
name: Laravel CI/CD
# Kapan workflow ini dijalankan
on:
push:
branches: [main] # Trigger saat push ke main
pull_request:
branches: [main] # Trigger saat ada PR ke main
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest # Jalankan di Ubuntu terbaru
# ... (isi di langkah berikutnya)
deploy:
name: Deploy to VPS
needs: test # Hanya jalan jika 'test' job berhasil
if: github.ref == 'refs/heads/main' # Hanya deploy dari branch main
# ... (isi di langkah berikutnya)
Langkah 2: CI — Jalankan Test Otomatis
test:
name: Run Tests
runs-on: ubuntu-latest
services:
# Setup MySQL sebagai service container
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: laravel_test
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout kode
uses: actions/checkout@v4
- name: Setup PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml, ctype, json, bcmath, pdo, pdo_mysql
coverage: xdebug # Jika butuh code coverage report
- name: Cache Composer packages
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Buat file .env untuk testing
run: |
cp .env.example .env.testing
echo "APP_KEY=" >> .env.testing
php artisan key:generate --env=testing
echo "DB_CONNECTION=mysql" >> .env.testing
echo "DB_HOST=127.0.0.1" >> .env.testing
echo "DB_PORT=3306" >> .env.testing
echo "DB_DATABASE=laravel_test" >> .env.testing
echo "DB_USERNAME=root" >> .env.testing
echo "DB_PASSWORD=password" >> .env.testing
- name: Jalankan migration
run: php artisan migrate --env=testing --force
- name: Jalankan semua test
run: php artisan test --parallel
Langkah 3: CD — Auto Deploy ke VPS via SSH
Siapkan GitHub Secrets
Di GitHub repository: Settings → Secrets and variables → Actions
Tambahkan secrets berikut:
| Secret | Nilai |
|---|---|
VPS_HOST | IP atau domain VPS |
VPS_USERNAME | Username SSH (contoh: deploy) |
VPS_SSH_KEY | Isi file private key (~/.ssh/id_rsa) |
VPS_PORT | Port SSH (default: 22) |
Cara generate SSH key untuk GitHub Actions:
# Generate key baru khusus GitHub Actions (jangan pakai key yang sudah ada)
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions_key
# Tambahkan public key ke VPS
ssh-copy-id -i ~/.ssh/github_actions_key.pub deploy@IP_VPS
# Copy isi private key → masukkan ke GitHub Secrets VPS_SSH_KEY
cat ~/.ssh/github_actions_key
Deploy Job
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deploy ke VPS via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
script: |
cd /var/www/task-manager
bash deploy.sh
- name: Notifikasi sukses
if: success()
run: echo "✅ Deploy berhasil ke production!"
- name: Notifikasi gagal
if: failure()
run: echo "❌ Deploy gagal! Cek logs."
Langkah 4: Script Deploy.sh
Gunakan script dari artikel deploy VPS yang diperkuat:
#!/bin/bash
# /var/www/task-manager/deploy.sh
set -e # Hentikan jika ada command yang gagal
echo "🚀 [$(date)] Memulai deployment..."
cd /var/www/task-manager
# Aktifkan maintenance mode
php artisan down --retry=60 --secret="bypass-secret-token-123"
# Pull kode terbaru
git pull origin main
# Install dependencies baru (tanpa dev packages)
composer install --optimize-autoloader --no-dev --no-interaction
# Build assets frontend jika ada perubahan
if git diff HEAD@{1} HEAD --name-only | grep -q "resources/"; then
echo "📦 Building frontend assets..."
npm ci && npm run build
fi
# Jalankan migration yang pending
php artisan migrate --force
# Bersihkan cache lama
php artisan optimize:clear
# Rebuild cache
php artisan optimize
# Restart queue workers
php artisan queue:restart
# Matikan maintenance mode
php artisan up
echo "✅ [$(date)] Deployment berhasil!"
Langkah 5: Environment Separation (Staging + Production)
# .github/workflows/laravel.yml — versi lengkap dengan staging
on:
push:
branches:
- main → deploy ke production
- develop → deploy ke staging
jobs:
deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop'
steps:
- uses: appleboy/[email protected]
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: cd /var/www/task-manager-staging && bash deploy.sh
deploy-production:
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: appleboy/[email protected]
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: cd /var/www/task-manager && bash deploy.sh
Langkah 6: Caching untuk Percepat CI
- name: Cache Composer
uses: actions/cache@v3
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- name: Cache npm
uses: actions/cache@v3
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
Dengan caching: CI yang biasanya 5 menit bisa turun ke 2 menit karena tidak perlu download ulang packages yang sama.
Workflow Lengkap: Final Version
# .github/workflows/laravel.yml
name: Laravel CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3'] # Test di multiple PHP version
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: laravel_test
ports: ['3306:3306']
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, xml, pdo, pdo_mysql, redis
coverage: none
- name: Cache Composer
uses: actions/cache@v3
with:
path: vendor
key: composer-${{ matrix.php }}-${{ hashFiles('composer.lock') }}
- run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Setup .env.testing
run: |
cp .env.example .env.testing
php artisan key:generate --env=testing
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_DATABASE: laravel_test
DB_USERNAME: root
DB_PASSWORD: password
- run: php artisan migrate --env=testing --force
- run: php artisan test --parallel
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deploy via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
script: cd /var/www/task-manager && bash deploy.sh
Troubleshooting: Error yang Sering Muncul
SSH: Permission denied (publickey)
Penyebab: Format private key salah saat copy ke GitHub Secrets, atau public key belum ditambahkan ke VPS.
Solusi:
# Pastikan public key ada di VPS
cat ~/.ssh/authorized_keys
# Harus terlihat key github-actions-deploy
# Copy private key dengan benar (termasuk header dan footer)
cat ~/.ssh/github_actions_key
# -----BEGIN OPENSSH PRIVATE KEY-----
# (isi key)
# -----END OPENSSH PRIVATE KEY-----
# Semua baris ini harus masuk ke GitHub Secret
# Test koneksi SSH manual dulu
ssh -i ~/.ssh/github_actions_key deploy@IP_VPS
Composer install timeout di CI
Penyebab: GitHub Actions runner butuh download semua package dari awal karena cache miss.
Solusi:
# Pastikan cache key benar — gunakan composer.lock bukan composer.json
- name: Cache Composer
uses: actions/cache@v3
with:
path: vendor
key: composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer- # Fallback ke cache apapun yang ada
Test lulus tapi deploy gagal
Penyebab: Environment variable berbeda di production, atau migration conflict.
Solusi:
# Cek output deploy.sh di GitHub Actions logs
# Atau jalankan manual di VPS untuk debug:
cd /var/www/task-manager && bash deploy.sh
# Jika migration conflict:
php artisan migrate:status
php artisan migrate --pretend # Lihat SQL tanpa eksekusi
MySQL service container connection refused
Penyebab: Port MySQL salah atau host harus 127.0.0.1 bukan localhost.
Solusi:
# Di .env.testing, gunakan 127.0.0.1 bukan localhost
DB_HOST: 127.0.0.1 # ← Bukan localhost!
# Pastikan port mapping benar
services:
mysql:
ports:
- 3306:3306 # host:container
Pertanyaan yang Sering Diajukan
GitHub Actions vs GitLab CI vs Jenkins, mana yang terbaik untuk Laravel?
GitHub Actions adalah pilihan terbaik jika kode sudah di GitHub — setup minimal, gratis untuk repo public, 2000 menit gratis per bulan untuk repo private. GitLab CI lebih powerful untuk enterprise dengan self-hosted runner. Jenkins untuk setup yang sangat custom, tapi complex.
Berapa lama waktu CI/CD yang ideal?
Target: under 5 menit dari push hingga deploy. Di atas 10 menit mulai mengganggu flow development. Optimalkan dengan: test paralel, caching dependencies, dan skip build step jika tidak ada perubahan assets.
Bagaimana cara rollback jika deploy gagal?
# di deploy.sh, tambahkan rollback capability
CURRENT_COMMIT=$(git rev-parse HEAD)
git pull origin main
if ! php artisan migrate --force; then
echo "Migration gagal! Rollback..."
git reset --hard $CURRENT_COMMIT
php artisan up
exit 1
fi
Atau lebih advanced: gunakan zero-downtime deployment dengan Symlink strategy (seperti Laravel Envoy atau Deployer.php).
Apakah GitHub Actions gratis?
Untuk repository public: gratis tanpa batas. Untuk repository private: 2.000 menit gratis per bulan (cukup untuk ~400 deployments). Setelah itu $0.008/menit. Untuk tim kecil, ini praktis tidak pernah terlampaui.
Kesimpulan
CI/CD adalah perbedaan antara deployment yang menakutkan dan deployment yang membosankan — dalam artian positif. Membosankan karena sudah otomatis dan dapat diandalkan.
Dengan workflow yang sudah kita buat:
- Setiap PR otomatis ditest
- Tidak ada kode yang belum ditest yang masuk ke main
- Deploy ke production cukup dengan
git push origin main - Rollback tersedia jika ada masalah
Ini adalah fondasi dari DevOps modern. Selanjutnya, pelajari cara monitoring aplikasi di production dengan Laravel Telescope — CI/CD yang baik perlu dilengkapi dengan observability yang baik!
Selamat — workflow development kamu sekarang setara dengan tim engineering perusahaan teknologi besar! 🚀🤖