Otomatisasi Deployment Tanpa Ribet Menggunakan GitHub Actions
Kenapa GitHub Actions?
GitHub Actions terintegrasi langsung dengan repo GitHub Anda. Tidak perlu setupCircleCI, Travis, atau Jenkins terpisah. Gratis untuk 2000 menit/bulan di akun publik — cukup untuk proyek small-medium.
Dalam project saya, GitHub Actions menghemat 4-6 jam per minggu pekerjaan manual deploy. Build -> test -> deploy -> notif Slack, semuanya otomatis.
Konsep Dasar Workflow
File .github/workflows/*.yml mendefinisikan workflow. Setiap workflow punya:
- Triggers: kapan jalan (
push,pull_request,schedule,workflow_dispatch) - Jobs: grup langkah yang berjalan di runner (Ubuntu, Windows, macOS)
- Steps: perintah individual dalam job
1. Workflow CI: Lint, Test, Build
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality:
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: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run typecheck
- name: Test
run: npm test -- --coverage
- name: Build
run: npm run build
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage/coverage-final.json
2. Deploy ke VPS tanpa Downtime
Strategi: pull kode baru, build lokal di VPS, restart service via systemd dengan zero downtime.
Server-side Setup
# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network.target
[Service]
Type=notify
User=deployer
WorkingDirectory=/var/www/myapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/node dist/server.js
Restart=always
RestartSec=3
KillSignal=SIGTERM
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target
Workflow Deployment
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm test
- run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
- name: Deploy to VPS
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
set -e
cd /var/www/myapp
git pull origin main
npm ci --production=false
npm run build
npm prune --production
sudo systemctl restart myapp
sleep 3
curl -f http://localhost:3000/health || exit 1
EOF
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-type: incoming-webhook
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "Deployment ${{ job.status == 'success' && '✅' || '❌' }} ${{ github.repository }}"
}
3. Secrets dan Environment Variables
Di GitHub repo → Settings → Secrets and variables → Actions:
DEPLOY_SSH_KEY (SSH private key)
SERVER_IP (VPS IP address)
SSH_USER (deployer)
SLACK_WEBHOOK (Slack webhook URL)
DATABASE_URL (production database URL)
JWT_SECRET (production JWT secret)
⚠️ Jangan commit secrets ke repo! Selaluvia GitHub secrets.
4. Deploy Composer PHP dengan SSH
name: Deploy PHP
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql, intl
coverage: none
- name: Install Composer Dependencies
run: composer install --no-dev --optimize-autoloader
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy via rsync
run: |
rsync -avz --delete \
--exclude='.git*' \
--exclude='node_modules' \
--exclude='.env' \
./ ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }}:/var/www/laravel-app/
- name: Run migrations on server
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
cd /var/www/laravel-app
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan octane:reload
EOF
5. Parallel Jobs dan Conditional
jobs:
build:
strategy:
matrix:
node: [18, 20, 22]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: ${{ matrix.node }}, cache: 'npm' }
- run: npm ci && npm test
notify:
needs: build
if: always()
runs-on: ubuntu-latest
steps:
- run: echo "All builds completed. Result: ${{ needs.build.result }}"
6. Caching Dependencies
- name: Cache node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
7. Build & Push Docker Image
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
myorg/myapp:latest
myorg/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
8. Health Check Setelah Deploy
- name: Wait for rollout
run: |
for i in {1..30}; do
if curl -sf https://myapp.com/health; then
echo "Health check passed"
exit 0
fi
sleep 5
done
echo "Health check failed"
exit 1
9. Rollback Otomatis
- name: Rollback on failure
if: failure()
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
cd /var/www/myapp
git rollback ${{ github.event.before }}
sudo systemctl restart myapp
EOF
Kesimpulan
GitHub Actions memungkinkan setup CI/CD lengkap dalam satu file YAML. Setup awal mungkin butuh 1-2 jam, tapi setelah itu, deploy jadi 0 menit pekerjaan manual. Push → test → deploy → notif → selesai.
Mulai dari workflow sederhana: lint + test saja. Tambah deploy setelah stabil. Tambah notification setelah diperlukan. Iterative improvement adalah kunci.