Deployment
IndieStack supports multiple deployment strategies. We recommend Vercel for one-click deployment, but Docker and traditional server deployment are also supported.
Vercel Deployment (Recommended)
Prerequisites
- Vercel account (sign in with GitHub)
- Git repository pushed to GitHub
One-Click Deploy
Vercel auto-detects Next.js and configures build settings:
# Install Vercel CLI
npm i -g vercel
# Log in to Vercel
vercel login
# Deploy to production
vercel --prodManual Deploy (Vercel Dashboard)
- Go to Vercel Dashboard, click "New Project"
- Import your GitHub repository
- Framework auto-detects Next.js (no manual selection needed)
- Add environment variables (copy required ones from
.env.example) - Click "Deploy"
Environment Variables
Add these in Vercel Dashboard → Project → Settings → Environment Variables:
| Environment | Source | Notes |
|---|---|---|
| Production | Vercel Dashboard → Settings → Environment Variables | Production config |
| Preview | Same as Production | Preview deploys inherit from Production |
| Development | .env.local | Local dev config |
Custom Domain
# Via CLI
vercel domains add yourdomain.com
# Or via Dashboard → Project → Settings → DomainsPreview Deployments
Pushing a PR or branch automatically creates a Preview deployment with a unique URL for team review.
Docker Deployment
Build Image
The project includes a multi-stage Dockerfile for optimized image size:
# Build Docker image
docker build -t indiestack .
# Run container
docker run -d -p 3000:3000 --env-file .env.production indiestackDocker Compose
The project includes docker-compose.yml with app + PostgreSQL:
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Stop services
docker compose downProduction Docker Compose
Create docker-compose.prod.yml for production:
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
env_file: .env.production
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3Traditional Server Deployment
PM2 + Nginx
# Build project
pnpm build
# Start with PM2
pnpm install -g pm2
pm2 start npm --name "indiestack" -- start
pm2 save
pm2 startupNginx reverse proxy configuration:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}GitHub Actions CI/CD
The .github/workflows/ directory contains pre-configured CI/CD workflows:
ci.yml — PR Checks
Runs on every push and PR:
- TypeScript type check (
pnpm type-check) - ESLint check (
pnpm lint) - Unit tests (
pnpm test) - Build verification (
pnpm build)
Deployment Method
This project uses Vercel's direct GitHub integration (no GitHub Actions deployment):
- Import the repository in Vercel
- The framework preset will be auto-detected as Next.js
- Add environment variables under Settings → Environment Variables
- Every
git pushtomainwill trigger automatic build and deploy on Vercel
The CI workflow only runs quality checks (lint / type-check / test / build) — it does not handle deployment.
Database Deployment
Supabase Production
- Create project in Supabase Dashboard
- Run database migrations:
npx supabase login
npx supabase link --project-ref your-project-ref
pnpm db:migrate- Enable Row Level Security:
npx supabase db push- Configure Auth settings (URLs, redirect domains, etc.)
Free-Tier Keepalive
Supabase free-tier projects get paused after 7 days without API activity. IndieStack keeps the project warm with two daily probes, both aimed at /api/health (which runs a select id from profiles limit 1 query through the service-role client):
| Layer | File | Schedule (UTC) | Notes |
|---|---|---|---|
| Vercel Cron (primary) | vercel.json | 0 2 * * * | Runs against production deployments only; does not expire |
| GitHub Actions (backup) | .github/workflows/health-check.yml | 17 3 * * * | GitHub disables schedule after 60 days without commits |
The GitHub Actions job reads the repository variable HEALTHCHECK_URL (Settings → Secrets and variables → Actions → Variables), e.g. https://your-domain.com/api/health; manual runs can override it with the health_url input. Both keepalive probes retry transient network errors, 5xx responses, and not-ready bodies up to three times with a five-second delay. Permanent errors such as 404/401 and sustained failures still fail loudly.
To turn the keepalive off, drop the crons block from vercel.json or the schedule trigger from the workflow — a paid Supabase plan makes the probes unnecessary.
Auto-Restore Fallback
Should the project ever get paused anyway (for example during a run of failed deploys, or when a probe is rate-limited), a fallback workflow restores it automatically:
| Layer | File | Schedule (UTC) | Notes |
|---|---|---|---|
| Vercel Cron (primary) | vercel.json → /api/ops/supabase-restore | 0 4 * * * | Survives repository silence; requires CRON_SECRET plus the management env vars below |
| GitHub Actions (backup) | .github/workflows/supabase-auto-restore.yml | 37 4 * * * | Calls the Management API only when the project status is INACTIVE |
Configure under Settings → Secrets and variables → Actions (for the workflow) and in the Vercel project environment (for the cron route):
- Variable
SUPABASE_PROJECT_REF— the Supabase project ref; on Vercel it may be omitted and inferred fromNEXT_PUBLIC_SUPABASE_URL(https://<ref>.supabase.co) - Secret
SUPABASE_ACCESS_TOKEN— Management API token (starts withsbp_, needsprojects:write) CRON_SECRET— Vercel Cron sends it automatically asAuthorization: Bearer <CRON_SECRET>
Both layers only restore when the Management API explicitly reports status=INACTIVE; transient states (RESTORING, COMING_UP, …) are left alone, and unknown or unrecoverable states fail loudly for a human to handle. scripts/supabase-auto-restore.js does not write anything when the project itself is healthy but the site is down (an application-side failure); manual workflow runs default to dry_run=true. The /api/ops/supabase-restore route returns 503 in production when its configuration is missing, so a silently disabled safety net is visible instead of hidden. Remember to update both secrets when the token is rotated.
Database Backup
# Backup with Supabase CLI
npx supabase db dump -f backup.sql
# Restore
npx supabase db import -f backup.sqlDocs Site Deployment
The docs site (VitePress) is a standalone static site that can be deployed independently:
Vercel
cd docs-site
# Install dependencies
pnpm install
# Build static files
pnpm build
# Deploy to Vercel
vercel --prodDocker
cd docs-site
# Build Docker image (Nginx-based)
docker build -t indiestack-docs .
# Run
docker run -d -p 8080:80 indiestack-docsNginx
docs-site/nginx.conf is pre-configured with Gzip, caching, and SPA fallback:
# Build static files
cd docs-site && pnpm build
# Deploy dist to Nginx
cp -r .vitepress/dist/* /var/www/docs/Pre-Launch Checklist
Before Going Live
- [ ] Verify all environment variables
- [ ] Run database migrations
- [ ] Enable Supabase RLS
- [ ] Configure Stripe Webhook
- [ ] Set up Sentry DSN
- [ ] Bind custom domain
- [ ] Configure SSL certificate
- [ ] Verify CI/CD workflows
- [ ] Run performance tests
- [ ] Verify error monitoring
- [ ] Check OG images and SEO tags
- [ ] Verify sitemap.xml and robots.txt
- [ ] Deploy and update docs site