CI/CD Deploy Pipeline
Full deploy pipeline: build, test, Docker build+push, SSH deploy, rollback strategy.
ci-cdgithub-actionsdevopsdeployment
# CI/CD Deploy Pipeline
## Full pipeline (GitHub Actions -> VPS)
```yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
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
environment: production
steps:
- uses: actions/checkout@v4
- name: Build & push Docker image
run: |
echo ${{ secrets.REGISTRY_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Deploy to VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/app
export IMAGE=ghcr.io/${{ github.repository }}:${{ github.sha }}
docker pull $IMAGE
docker stop api || true
docker run -d --rm --name api \
-p 3000:3000 \
--env-file /opt/app/.env \
$IMAGE
echo "Deployed $IMAGE at $(date)"
```
## Secrets to set in GitHub
```
VPS_HOST # server IP or domain
SSH_PRIVATE_KEY # private key (ssh-keygen -t ed25519)
REGISTRY_TOKEN # GitHub PAT with packages:write
```
## Deploy with Docker Compose
```bash
# On server deploy script
cd /opt/app
git pull origin main
echo "IMAGE_TAG=${{ github.sha }}" > .env.deploy
docker compose -f compose.yml -f compose.prod.yml pull
docker compose -f compose.yml -f compose.prod.yml up -d --no-build
docker image prune -f
```
## Blue-Green deployment
```bash
# Run new version on port 3001
docker run -d --name api-green -p 3001:3000 $NEW_IMAGE
# Health check
curl -f http://localhost:3001/health || exit 1
# Switch nginx upstream
sed -i 's/3000/3001/' /etc/nginx/conf.d/api.conf
nginx -s reload
# Stop old version
docker stop api-blue
```
## Rollback
```bash
# Tag images with git SHA, keep last 3
# Rollback = re-run deploy with previous SHA
git log --oneline -5 # get previous SHA
# In GitHub: re-run workflow on previous commit
# Or manually:
docker run -d --name api -p 3000:3000 ghcr.io/org/api:PREVIOUS_SHA
```
## Health check endpoint
```ts
app.get('/health', async (req, res) => {
try {
await db.query('SELECT 1') // check DB
res.json({ status: 'ok', uptime: process.uptime(), ts: Date.now() })
} catch {
res.status(503).json({ status: 'error' })
}
})
```
## Notification on deploy
```yaml
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v1
with:
payload: '{"text":"Deploy ${{ job.status }}: ${{ github.repository }}@${{ github.sha }}"}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
```API: /api/skills/ci-cd-deploy-pipeline