GitHub Actions can do a lot more than run your test suite. With the right workflow design, it becomes an autonomous deployment pipeline that tests, builds, scans for vulnerabilities, deploys, and notifies — without manual intervention.
The Complete CI/CD Workflow
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: testdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
- run: npm test
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
build:
needs: 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 run build
- uses: actions/upload-artifact@v4
with:
name: build
path: .next
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: .next
- name: Deploy to Vercel
run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
Dependency Caching
The cache: npm option on setup-node caches ~/.npm based on package-lock.json. This cuts install time from 60s to ~5s on cache hits.
For Docker builds, cache layers explicitly:
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
Secrets Management
Store secrets in GitHub Settings > Secrets and Variables > Actions. Reference them in workflows:
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
Never hardcode secrets. Never echo them in logs (GitHub masks them, but be explicit).
Parallel Jobs
Run independent checks in parallel to reduce total CI time:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npx tsc --noEmit
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy:
needs: [lint, typecheck, test] # all three must pass
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo 'Deploying...'
PR Preview Deployments
Deploy every PR to a unique preview URL:
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy preview
id: deploy
run: |
URL=$(npx vercel --token ${{ secrets.VERCEL_TOKEN }})
echo "url=$URL" >> $GITHUB_OUTPUT
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Preview: ${{ steps.deploy.outputs.url }}'
})
Security Scanning
Add automated vulnerability scanning:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
The Ship Fast Skill Pack at whoffagents.com includes a /deploy skill that generates complete GitHub Actions workflows — test, build, Docker, Vercel, and security scanning — customized for your stack. $49 one-time.
Top comments (0)