CI/CD Pipeline with GitHub Actions
Overview
| Created | Updated | Status |
|---|---|---|
| 2026-01-28 | 2026-01-31 | ✅ Accepted |
Context
Automate deployments from GitHub to VPS for Next.js frontend, Hono API, and Docusaurus docs.
Decision: GitHub Actions with SSH deployment. Build Docker images on VPS (not Docker Hub) for simplicity.
Architecture
GitHub Repository
├─ push dev → SSH → Development VPS (app.quangnguyen.dev)
└─ push main → SSH → Production VPS (quangnguyen.com)
Deploy script: git pull → docker compose build → up -d → prune
Workflow
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [dev, main]
workflow_dispatch:
jobs:
deploy-dev:
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
environment: development
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /var/www/app_quangnguyen_dev/data/www/app.quangnguyen.dev
git pull origin dev
docker compose -f docker-compose.prod.yml up -d --build
docker image prune -f
deploy-prod:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PROD_VPS_HOST }}
username: ${{ secrets.PROD_VPS_USER }}
key: ${{ secrets.PROD_VPS_SSH_KEY }}
script: |
cd ${{ secrets.PROD_APP_PATH }}
git pull origin main
docker compose -f docker-compose.prod.yml up -d --build
docker image prune -f
GitHub Secrets
| Environment | Secrets |
|---|---|
| Development | VPS_HOST, VPS_USER, VPS_SSH_KEY |
| Production | PROD_VPS_HOST, PROD_VPS_USER, PROD_VPS_SSH_KEY, PROD_APP_PATH |
Manual Deploy Script
#!/bin/bash
# deploy.sh
set -e
git pull origin main
SERVICE=${1:-"all"}
deploy_service() {
docker compose -f docker-compose.prod.yml build $1
docker compose -f docker-compose.prod.yml up -d --no-deps --wait $1
}
[ "$SERVICE" = "all" ] && { deploy_service api; deploy_service web; } || deploy_service $SERVICE
docker image prune -f
Usage: ./deploy.sh api or ./deploy.sh web or ./deploy.sh
Rollback
git log --oneline -10
git checkout <commit-hash>
docker compose -f docker-compose.prod.yml up -d --build
Alternatives Considered
| Option | Rejected Because |
|---|---|
| Docker Hub | Extra step, registry costs |
| GitHub Container Registry | Similar overhead |
| Kubernetes/ArgoCD | Overkill for single-server |
Consequences
Positive: Simple setup, fast deploys, easy debugging via SSH Negative: Builds use VPS resources, no image versioning in registry
Security
- SSH keys in GitHub Secrets (encrypted)
- Dedicated deploy user with docker group only
- Never commit
.env.prodfiles