A pipeline is not just “automatic deployment”. It is to reduce risk per release and improve recovery time when something goes wrong.
##Pipeline goal
- reproducible build,
- automatic validation,
- predictable deployment,
- clear rollback.
Recommended flow
- Push to
main. - Build + checks.
- Deploy via SSH to VPS.
- Restart controlled with PM2.
- Health checks and basic monitoring.
Base workflow in GitHub Actions
Minimal example to build + deploy via SSH when there is a push to main:
name: ci-cd-vps
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install
run: npm ci
- name: Build
run: npm run build
- name: Deploy via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
/var/www/app/scripts/deploy.sh
VPS deploy script
Separating the deploy into a script avoids long commands and reduces shell errors within the workflow:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/var/www/app"
RELEASES_DIR="$APP_DIR/releases"
CURRENT_LINK="$APP_DIR/current"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
NEW_RELEASE="$RELEASES_DIR/$TIMESTAMP"
mkdir -p "$NEW_RELEASE"
git -C "$APP_DIR" fetch origin main
git -C "$APP_DIR" archive origin/main | tar -x -C "$NEW_RELEASE"
cp "$APP_DIR/shared/.env" "$NEW_RELEASE/.env"
npm --prefix "$NEW_RELEASE" ci --omit=dev
npm --prefix "$NEW_RELEASE" run build
ln -sfn "$NEW_RELEASE" "$CURRENT_LINK"
pm2 startOrReload "$APP_DIR/shared/ecosystem.config.js" --update-env
##Smoke test post deploy
A simple check avoids leaving broken release as “success”:
#!/usr/bin/env bash
set -euo pipefail
URL="https://tu-dominio.com/health"
for i in {1..10}; do
if curl -fsS "$URL" >/dev/null; then
echo "health ok"
exit 0
fi
sleep 3
done
echo "health failed"
exit 1
Non-negotiable security points
- secrets only on GitHub Secrets,
- SSH keys with minimum permissions,
- periodic key rotation,
- active security headers in app and nginx.
Typical errors in CI/CD VPS
.envbad generated by heredoc indentation,- dependency installed on runner but not on server,
- restart without checking health,
- deploy that steps on artifacts without versioning.
Secure Deploy Checklist
- clean build
- quick backup of previous release
- supported migrations
- restart controlled
- smoke test of critical paths
Happy reading! ☕
Comments