Skip to main content

Tutorial: Set Up a Deployment Pipeline

Create a complete CI/CD pipeline for your AutoDeployBase project.

Overview

We'll set up:

  • GitHub Actions for CI
  • Automated testing
  • Database migrations
  • Staging and production deployments
  • Vercel + PostgreSQL

Prerequisites

  • GitHub repository
  • Vercel account
  • PostgreSQL database (Vercel Postgres or Neon)

Step 1: Create the Project

npx autodeploybase init my-app \
--framework next \
--archetype saas \
--database postgresql \
--plugins github-actions

cd my-app
git init
git add .
git commit -m "Initial commit"

Step 2: Configure GitHub Secrets

Go to your GitHub repo → Settings → Secrets → Actions

Add these secrets:

SecretDescription
VERCEL_TOKENVercel API token
VERCEL_ORG_IDVercel organization ID
VERCEL_PROJECT_IDVercel project ID
DATABASE_URLProduction database URL
DATABASE_URL_STAGINGStaging database URL
JWT_SECRETJWT secret key

Get Vercel IDs

vercel link
cat .vercel/project.json

Step 3: CI Workflow

.github/workflows/ci.yml
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

env:
NODE_VERSION: '20'

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- run: npm ci
- run: npm run lint

typecheck:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- run: npm ci
- run: npx prisma generate
- run: npm run typecheck

test:
name: Test
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
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: ${{ env.NODE_VERSION }}
cache: 'npm'

- run: npm ci
- run: npx prisma generate

- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test

- name: Run tests
run: npm test -- --coverage
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test
JWT_SECRET: test-secret-for-ci

- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info

build:
name: Build
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- run: npm ci
- run: npx prisma generate
- run: npm run build

Step 4: Staging Deployment

.github/workflows/deploy-staging.yml
name: Deploy Staging

on:
push:
branches: [develop]

jobs:
deploy:
name: Deploy to Staging
runs-on: ubuntu-latest
environment: staging

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci

- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL_STAGING }}

- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
# No --prod flag = preview deployment
env:
VERCEL_ENV: staging

Step 5: Production Deployment

.github/workflows/deploy-production.yml
name: Deploy Production

on:
push:
branches: [main]

jobs:
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
environment: production

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci

- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}

- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'

- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
channel-id: 'deployments'
slack-message: 'Production deployed: ${{ github.sha }}'
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Step 6: Database Migration Workflow

For manual migrations:

.github/workflows/migrate.yml
name: Database Migration

on:
workflow_dispatch:
inputs:
environment:
description: 'Environment'
required: true
type: choice
options:
- staging
- production

jobs:
migrate:
name: Run Migrations
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci

- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || secrets.DATABASE_URL_STAGING }}

- name: Verify
run: npx prisma db pull --force
env:
DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || secrets.DATABASE_URL_STAGING }}

Step 7: Preview Deployments for PRs

.github/workflows/preview.yml
name: Preview Deployment

on:
pull_request:
types: [opened, synchronize]

jobs:
preview:
name: Deploy Preview
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Deploy Preview
uses: amondnet/vercel-action@v25
id: deploy
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

- 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 deployed: ${{ steps.deploy.outputs.preview-url }}'
})

Step 8: Vercel Configuration

vercel.json
{
"framework": "nextjs",
"buildCommand": "prisma generate && next build",
"regions": ["iad1"],
"env": {
"DATABASE_URL": "@database-url",
"JWT_SECRET": "@jwt-secret"
},
"functions": {
"api/**/*.ts": {
"maxDuration": 30
}
}
}

Workflow Diagram

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Developer │────▶│ GitHub │────▶│ Vercel │
│ Push │ │ Actions │ │ Deploy │
└─────────────┘ └─────────────┘ └─────────────┘


┌─────────────┐
│ Tests │
│ Lint │
│ Build │
└─────────────┘

┌────────────┴────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Staging │ │ Production │
│ (develop) │ │ (main) │
└─────────────┘ └─────────────┘

Monitoring

Add monitoring after deployment:

- name: Health Check
run: |
sleep 30
curl -f ${{ steps.deploy.outputs.preview-url }}/api/health || exit 1

Rollback

For quick rollback:

# List deployments
vercel ls

# Rollback to previous
vercel rollback

Or in workflow:

- name: Rollback on failure
if: failure()
run: vercel rollback --token ${{ secrets.VERCEL_TOKEN }}