Setup CI/CD Laravel dengan GitHub Actions - Zero Downtime Deployment

January 16, 2025 (10mo ago)

๐Ÿš€ Tutorial Lengkap: Setup CI/CD Laravel dengan GitHub Actions - Zero Downtime Deployment

Halo developer! ๐Ÿ‘‹

Pernah nggak sih kalian deploy aplikasi Laravel, terus website mati beberapa detik? User complain, boss marah-marah, dan kalian cuma bisa bilang "Bentar pak, lagi deploy". Nah, artikel ini bakal ngajarin kalian cara deploy Laravel dengan ZERO DOWNTIME menggunakan GitHub Actions. Jadi website tetep hidup meskipun lagi deploy. Keren kan?

๐Ÿ“š Table of Contents


๐Ÿค” Kenapa Harus CI/CD?

Bayangin kalian deploy manual kayak gini:

Pull latest code

git pull origin master

Install dependencies

composer install npm run build

Run migrations

php artisan migrate

Clear cache

php artisan cache:clear

Website mati 30 detik - 2 menit ๐Ÿ’€

</CodeBlock>

<Alert variant="destructive">
  <AlertTitle>Masalahnya:</AlertTitle>
  <AlertDescription>
    - โŒ Website mati sementara (downtime)<br/>
    - โŒ Kalau gagal, user lihat error page<br/>
    - โŒ Manual = cape = prone to human error<br/>
    - โŒ Rollback susah kalau ada masalah
  </AlertDescription>
</Alert>

<Alert variant="success">
  <AlertTitle>Dengan CI/CD + Zero Downtime:</AlertTitle>
  <AlertDescription>
    - โœ… Website tetap online 24/7<br/>
    - โœ… Otomatis deploy setiap push ke GitHub<br/>
    - โœ… Rollback cepat kalau ada masalah<br/>
    - โœ… Tidur nyenyak karena deploy aman
  </AlertDescription>
</Alert>

---

## ๐ŸŽฏ What is Zero Downtime Deployment?

Konsepnya simpel tapi genius. Instead of updating files directly, kita pakai **symlink strategy**:

<FileTree>

Production Server: โ”œโ”€โ”€ current -> releases/release_20250116_120000 โ† User akses ini โ”œโ”€โ”€ releases/ โ”‚ โ”œโ”€โ”€ release_20250116_120000/ โ† NEW (sedang prepare) โ”‚ โ””โ”€โ”€ release_20250116_110000/ โ† OLD (sedang jalan) โ””โ”€โ”€ shared/ โ”œโ”€โ”€ .env โ””โ”€โ”€ storage/

</FileTree>

<Callout type="success" title="Deployment Process">
1. Upload release baru ke folder `releases/release_TIMESTAMP`
2. Setup symlink, install dependencies, migrate database
3. **SWITCH** symlink `current` ke release baru (atomic, 0.001 detik!)
4. Website langsung pakai release baru
5. Cleanup old releases (keep 5 releases terakhir)
</Callout>

Website nggak pernah mati karena perpindahannya **instant**! ๐Ÿš€

---

## ๐Ÿ“‹ Requirements

Sebelum mulai, pastikan kalian punya:

<Tabs defaultValue="local">
  <TabsList>
    <TabsTrigger value="local">Local Development</TabsTrigger>
    <TabsTrigger value="server">Production Server</TabsTrigger>
  </TabsList>
  
  <TabsContent value="local">
    - โœ… Laravel project (Laravel 10/11)
    - โœ… Git repository di GitHub
    - โœ… Terminal / Command Prompt
    - โœ… Kopi/teh โ˜• (biar semangat)
  </TabsContent>
  
  <TabsContent value="server">
    - โœ… VPS (Ubuntu 20.04 / 22.04 / 24.04)
    - โœ… PHP 8.2+
    - โœ… MySQL / MariaDB
    - โœ… Nginx / Apache
    - โœ… Composer
    - โœ… Node.js & NPM
    - โœ… SSH access
  </TabsContent>
</Tabs>

<Callout type="info">
Tutorial ini support aaPanel, tapi bisa juga untuk VPS biasa atau hosting control panel lainnya.
</Callout>

---

## ๐Ÿ—๏ธ Arsitektur Deployment

Mari kita lihat **big picture**-nya dulu:

```mermaid
graph TB
    A[Developer Push Code] -->|git push| B[GitHub Repository]
    B -->|Trigger| C[GitHub Actions]
    C -->|1. Build Assets| D[npm run build]
    C -->|2. Install Dependencies| E[composer install]
    C -->|3. Create Bundle| F[Release Package]
    F -->|SSH Upload| G[Production Server]
    G -->|4. Extract| H[/releases/new]
    H -->|5. Symlink| I[/current]
    I -->|6. Serve| J[Nginx/Apache]
    J --> K[Users ๐ŸŽ‰]
    
    style C fill:#2ea44f
    style G fill:#0969da
    style K fill:#ffd700

๐Ÿ› ๏ธ Step 1: Server Preparation

1.1 - Create Directory Structure

SSH ke server kalian, terus jalankan:

Buat struktur folder

sudo mkdir -p $DEPLOY_PATH/releases sudo mkdir -p $DEPLOY_PATH/shared/storage/app/public sudo mkdir -p $DEPLOY_PATH/shared/storage/logs sudo mkdir -p $DEPLOY_PATH/shared/storage/framework/cache sudo mkdir -p $DEPLOY_PATH/shared/storage/framework/sessions sudo mkdir -p $DEPLOY_PATH/shared/storage/framework/views

Set ownership (ganti 'www' dengan user web server kalian)

sudo chown -R www:www $DEPLOY_PATH sudo chmod -R 755 $DEPLOY_PATH sudo chmod -R 775 $DEPLOY_PATH/shared/storage

</CodeBlock>

<Callout type="warning">
**Pro tip:** Kalau pakai aaPanel, user-nya biasanya `www`. Kalau VPS biasa bisa `www-data` atau `ubuntu`.
</Callout>

### 1.2 - Setup .env File

<CodeBlock language="bash" title="Create Environment File">
```bash
# Buat file .env di shared directory
sudo nano /var/www/production/shared/.env

Paste konfigurasi ini (sesuaikan dengan database kalian):

DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_username DB_PASSWORD=super_secret_password

CACHE_DRIVER=file QUEUE_CONNECTION=database SESSION_DRIVER=file

... config lainnya

</CodeBlock>

<Alert variant="warning">
  <AlertTitle>Security First!</AlertTitle>
  <AlertDescription>
    Jangan lupa set proper permissions:
    ```bash
    sudo chmod 600 /var/www/production/shared/.env
    sudo chown www:www /var/www/production/shared/.env
    ```
  </AlertDescription>
</Alert>

### 1.3 - Database Setup

Kalau belum punya database, buat dulu:

<CodeBlock language="sql" title="MySQL Database Setup">
```bash
# Login ke MySQL
mysql -u root -p

# Buat database dan user
CREATE DATABASE laravel_production 
  CHARACTER SET utf8mb4 
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'laravel_user'@'localhost' 
  IDENTIFIED BY 'your_secure_password';

GRANT ALL PRIVILEGES ON laravel_production.* 
  TO 'laravel_user'@'localhost';

FLUSH PRIVILEGES;
EXIT;

Test koneksi:

1.4 - SSH Key Configuration

Di komputer lokal kalian:

Copy ke server

ssh-copy-id www@your-server-ip

Test SSH

ssh www@your-server-ip "echo 'Connected!'"

</CodeBlock>

<Callout type="info">
**Important:** Nanti private key-nya akan kita masukkan ke GitHub Secrets.
</Callout>

### 1.5 - Supervisor Setup (Queue Workers)

<CodeBlock language="bash" title="Install Supervisor">
```bash
# Install supervisor
sudo apt install supervisor -y

# Buat config file
sudo nano /etc/supervisor/conf.d/laravel-worker.conf

Paste configuration ini:

Start supervisor:


โš™๏ธ Step 2: GitHub Actions Setup

Sekarang bagian yang seru! Kita bikin automated deployment workflow di GitHub.

2.1 - Configure GitHub Secrets

Buka repository GitHub kalian, terus:

  1. Klik Settings
  2. Klik Secrets and variables โ†’ Actions
  3. Klik New repository secret

Tambahkan secrets berikut:

Secret Name Value Example
SSH_PRIVATE_KEY Private SSH key kalian -----BEGIN RSA PRIVATE KEY-----...
SSH_USERNAME Username SSH www atau ubuntu
SSH_HOST IP/hostname server 192.168.1.100
SSH_PORT SSH port 22
PRODUCTION_PATH Path deployment production /var/www/production
DEVELOPMENT_PATH Path deployment development /var/www/development

2.2 - Create Workflow File

Di repository Laravel kalian, buat file structure ini:

Paste workflow configuration ini:

on: push: branches: - master # Deploy ke production - development # Deploy ke development

env: KEEP_RELEASES: 5 PHP_VERSION: '8.2' NODE_VERSION: '20'

jobs: build_and_deploy: name: Build & Zero-downtime Deploy runs-on: ubuntu-latest

steps:
  - name: ๐Ÿ“ฅ Checkout repository
    uses: actions/checkout@v4
    with:
      fetch-depth: 0

  - name: ๐Ÿ˜ Setup PHP
    uses: shivammathur/setup-php@v2
    with:
      php-version: ${{ env.PHP_VERSION }}
      extensions: mbstring, xml, ctype, curl, bcmath, pdo_mysql, gd, zip
      coverage: none

  - name: ๐Ÿ“ฆ Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: ${{ env.NODE_VERSION }}
      cache: 'npm'

  - 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@v3
    with:
      path: ${{ steps.composer-cache.outputs.dir }}
      key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
      restore-keys: ${{ runner.os }}-composer-

  - name: ๐ŸŽผ Install Composer dependencies
    run: composer install --no-interaction --prefer-dist --no-dev --optimize-autoloader

  - name: ๐Ÿ“ฆ Install NPM dependencies
    run: npm ci

  - name: ๐Ÿ—๏ธ Build assets with Vite
    run: npm run build

  - name: ๐Ÿ“ฆ Prepare release bundle
    id: prepare_release
    run: |
      TIMESTAMP=$(date +%Y%m%d%H%M%S)
      RELEASE_DIR="release_${TIMESTAMP}"
      mkdir -p "$RELEASE_DIR"
      
      rsync -a \
        --exclude='.git' \
        --exclude='node_modules' \
        --exclude='tests' \
        --exclude='.github' \
        ./ "$RELEASE_DIR/"
      
      mkdir -p "$RELEASE_DIR/storage/logs"
      mkdir -p "$RELEASE_DIR/bootstrap/cache"
      
      echo "RELEASE_DIR=$RELEASE_DIR" >> $GITHUB_OUTPUT

  - name: ๐Ÿ” Add SSH key
    uses: webfactory/ssh-agent@v0.9.0
    with:
      ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

  - name: ๐Ÿš€ Deploy to server
    env:
      SSH_USER: ${{ secrets.SSH_USERNAME }}
      SSH_HOST: ${{ secrets.SSH_HOST }}
      SSH_PORT: ${{ secrets.SSH_PORT }}
      PRODUCTION_PATH: ${{ secrets.PRODUCTION_PATH }}
      DEVELOPMENT_PATH: ${{ secrets.DEVELOPMENT_PATH }}
    run: |
      # ... deployment script (check full version di repo)
      echo "๐ŸŽ‰ Deployment successful!"

  - name: โœ… Verify deployment
    run: echo "Website is running!"
</CodeBlock>

<Callout type="success">
Check full workflow code di repository atau download dari link di akhir artikel!
</Callout>

### 2.3 - Commit & Push

<CodeBlock language="bash" title="Deploy Your Workflow">
```bash
git add .github/workflows/deploy.yml
git commit -m "feat: add CI/CD deployment workflow ๐Ÿš€"
git push origin master

Sekarang cek tab Actions di GitHub repository kalian. Kalau semua benar, akan ada workflow yang sedang running! ๐ŸŽ‰


๐ŸŽจ Step 3: aaPanel Configuration (Bonus)

Kalau kalian pakai aaPanel, ada konfigurasi khusus yang perlu dilakukan.

3.1 - Update Nginx Configuration

# PENTING: Point ke current/public (symlink)
root /www/wwwroot/your-domain.com/current/public;

index index.php index.html;
charset utf-8;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# Laravel routing
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
    fastcgi_pass unix:/tmp/php-cgi-82.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

}

    </CodeBlock>
  </TabsContent>
  
  <TabsContent value="apache">
    <CodeBlock language="apache" title="Apache Configuration">
```apache
<VirtualHost *:80>
    ServerName your-domain.com
    DocumentRoot /www/wwwroot/your-domain.com/current/public

    <Directory /www/wwwroot/your-domain.com/current/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
</CodeBlock>

3.2 - Setup Sudo Permissions

Tambahkan baris ini di paling bawah:

3.3 - SSL Certificate

Boom! Website kalian sekarang HTTPS! ๐Ÿ”’


๐Ÿงช Step 4: Testing Deployment

Saatnya test deployment system kita!

git add . git commit -m "test: CI/CD deployment ๐Ÿš€" git push origin master

    </CodeBlock>
    
    <Callout type="success">
    Buka GitHub โ†’ tab **Actions**, watch the magic happen! ๐ŸŽฌ
    </Callout>
  </TabsContent>
  
  <TabsContent value="manual">
    <CodeBlock language="bash" title="Manual Deployment Test">
```bash
export SSH_USER="www"
export SSH_HOST="192.168.1.100"
export DEPLOY_PATH="/var/www/production"

chmod +x deploy-test.sh
./deploy-test.sh
</CodeBlock>

Verify Deployment

Check current release

ls -la /var/www/production/current

Check Laravel version

cd /var/www/production/current php artisan --version

View logs

tail -f storage/logs/laravel.log

</CodeBlock>

Buka website kalian di browser. Kalau muncul, berarti **SUKSES!** ๐ŸŽ‰๐ŸŽŠ

---

## ๐Ÿ”™ Step 5: Rollback Strategy

Waduh, ada bug di production? **Don't panic!** Kita bisa rollback dengan cepat.

### Quick Rollback (Manual)

<CodeBlock language="bash" title="Emergency Rollback">
```bash
# SSH ke server
ssh www@your-server-ip

# List available releases
ls -lt /var/www/production/releases

# Switch ke release sebelumnya
cd /var/www/production
ln -sfn releases/release_20250116110000 current

# Clear cache
cd current
php artisan config:clear
php artisan cache:clear
php artisan queue:restart

Website langsung balik ke versi sebelumnya dalam hitungan detik!

Automated Rollback Script

chmod +x rollback.sh ./rollback.sh

</CodeBlock>

<Callout type="success" title="Auto Rollback Features">
Script ini akan otomatis:
1. โœ… Tampilkan list releases
2. โœ… Switch ke release sebelumnya
3. โœ… Clear cache
4. โœ… Restart workers
</Callout>

---

## ๐Ÿ› Troubleshooting

### Problem 1: "Permission Denied"

<Alert variant="destructive">
  <AlertTitle>Error:</AlertTitle>
  <AlertDescription>
    ```
    Permission denied (publickey)
    ```
  </AlertDescription>
</Alert>

<Callout type="info" title="Solution">
```bash
# Pastikan SSH key sudah di-add ke server
ssh-copy-id www@your-server-ip

# Test koneksi
ssh www@your-server-ip "echo 'Works!'"

Problem 2: "Database Connection Failed"

Recreate user

DROP USER IF EXISTS 'laravel_user'@'localhost'; CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'new_password'; GRANT ALL ON laravel_production.* TO 'laravel_user'@'localhost'; FLUSH PRIVILEGES;

    </CodeBlock>
  </TabsContent>
</Tabs>

### Problem 3: "Assets Not Loading (404)"

<Callout type="warning" title="Fix Storage Symlink">
```bash
cd /var/www/production/current
rm -f public/storage
ln -sfn ../storage/app/public public/storage
chmod -R 775 storage

Common Issues Reference

Issue Quick Fix Script
Database error ./fix-database.sh Auto-detect & fix
Permission denied chown -R www:www /path Fix ownership
Assets 404 Recreate storage symlink ln -sfn
Queue stuck supervisorctl restart all Restart workers

๐Ÿ’ก Pro Tips & Best Practices

1. Multiple Environments

Setup berbagai environment untuk workflow yang lebih baik:

2. Slack/Discord Notification

Get notified setiap deployment:

3. Run Tests Before Deploy

Safety first! Test dulu sebelum deploy:

Kalau test gagal, deployment otomatis dibatalkan.

4. Database Backup Strategy

Keep 30 days backup

find /backup -name "*.sql" -mtime +30 -delete

</CodeBlock>

### 5. Health Check After Deploy

Pastikan website beneran jalan:

<CodeBlock language="yaml" title="Health Check Step">
```yaml
- name: ๐Ÿฅ Health Check
  run: |
    RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://your-domain.com)
    if [ $RESPONSE -ne 200 ]; then
      echo "Health check failed! Rolling back..."
      exit 1
    fi

๐ŸŽ“ Conclusion

Selamat! ๐ŸŽŠ Kalian sekarang punya deployment system yang:

What's Next?

๐Ÿ“ฅ Download Resources

Download Package โ†’

๐Ÿ™‹ FAQ


๐Ÿ“š External Resources


Tags: #Laravel #CI/CD #GitHub #Actions #Deployment #ZeroDowntime #DevOps #PHP #Tutorial #Indonesian


Artikel ini ditulis dengan โค๏ธ dan banyak โ˜• by developer yang udah capek deploy manual.

Last updated: 16 Januari 2025