Back to blog
CI/CD for Astro / Next.js with GitHub Actions
A complete workflow: lint → test → build → deploy to in-house servers. With caching and safe rollback.
Goals
- Push to
main→ auto-deploy to production - PR → run lint + test + preview build
- Cheap deploys: cache deps, don’t reinstall everything
- Safe rollback if a deploy goes wrong
.github/workflows/deploy.yml
name: Build & Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- name: Type check
run: npx astro check
- name: Build
run: npm run build
- name: Pack dist
run: tar --exclude='._*' -czf dist.tar.gz -C dist .
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist.tar.gz
retention-days: 7
deploy:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
- name: Deploy via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
source: dist.tar.gz
target: /tmp/
- name: Atomic swap
uses: appleboy/[email protected]
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
set -euo pipefail
STAGE=/var/www/site/staging
mkdir -p "$STAGE"
find "$STAGE" -mindepth 1 -delete 2>/dev/null
tar -xzf /tmp/dist.tar.gz -C "$STAGE"
mv /var/www/site/dist /var/www/site/dist.old
mv "$STAGE" /var/www/site/dist
chown -R www-data:www-data /var/www/site/dist
find /var/www/site/dist.old -mindepth 1 -delete
rmdir /var/www/site/dist.old
rm /tmp/dist.tar.gz
Set up secrets
Repo Settings → Secrets and variables → Actions:
DEPLOY_HOST— server IPDEPLOY_USER— typicallyrootordeployDEPLOY_SSH_KEY— content of~/.ssh/id_ed25519(private key)
Generate a deploy-only key:
ssh-keygen -t ed25519 -f deploy_key -C "github-deploy"
ssh-copy-id -i deploy_key.pub root@your-server
cat deploy_key # paste into the DEPLOY_SSH_KEY secret
Tips to keep builds fast
- Cache
~/.npm—cache: 'npm'on setup-node. - Cache the Astro
.astrofolder —actions/cache@v4keyed byastro.config.mjshash. - Split jobs — build and deploy can run in parallel for multi-env.
- Skip CI for doc-only commits with
[skip ci]in the message.
Rollback strategy
dist.old on the server is the previous version. If a fresh deploy is bad:
ssh server
mv /var/www/site/dist /var/www/site/dist.broken
mv /var/www/site/dist.old /var/www/site/dist
Add a one-liner rollback.sh:
#!/bin/bash
set -e
mv /var/www/site/dist /var/www/site/dist.broken
mv /var/www/site/dist.old /var/www/site/dist
echo "Rolled back."