| name | deploying-to-production |
| description | This skill should be used when deploying applications to production environments including Vercel, Fly.io, Hostinger VPS, or other platforms - covers environment management, zero-downtime deployments, Docker best practices, database migrations, rollback procedures, and health monitoring. |
Deploying to Production
Overview
Deploy applications safely and reliably across multiple platforms. This skill provides systematic deployment workflows, environment management, and operational best practices.
Core principle: Deployments should be boring, predictable, and reversible.
When to Use
Use this skill when:
- Deploying applications to production
- Setting up CI/CD pipelines
- Configuring environment variables and secrets
- Planning database migrations
- Implementing zero-downtime deployment strategies
- Setting up monitoring and health checks
- Planning rollback procedures
Platforms covered:
- Vercel (Next.js, frontend apps)
- Fly.io (full-stack apps, APIs)
- Hostinger VPS (Docker, manual deployments)
- General Docker/docker-compose patterns
Platform Selection Guide
| Platform | Best For | Deploy Time | Cost (Est.) |
|---|---|---|---|
| Vercel | Next.js, Static sites, Serverless APIs | <2 min | Free tier generous, $20+/mo pro |
| Fly.io | Full-stack apps, APIs, Databases | <5 min | $0-10/mo small apps |
| Hostinger VPS | Full control, Docker apps, Databases | <10 min | Fixed VPS cost |
| Railway | Quick prototypes, Postgres + App | <3 min | $5+/mo |
| Render | Web services, Cron jobs, Databases | <5 min | Free tier, $7+/mo |
Vercel Deployment
Initial Setup
# Install Vercel CLI
npm i -g vercel
# Login
vercel login
# Deploy from project directory
vercel
# Production deployment
vercel --prod
Environment Variables
Via CLI:
# Add environment variable
vercel env add VARIABLE_NAME
# Pull environment variables locally
vercel env pull .env.local
Via Dashboard:
- Project Settings → Environment Variables
- Add variables for each environment (Production, Preview, Development)
- Redeploy for changes to take effect
Automatic Deployments
Setup via GitHub:
- Push code to GitHub
- Import project in Vercel dashboard
- Configure build settings:
- Framework Preset: Next.js (auto-detected)
- Build Command:
npm run build - Output Directory:
.next(auto-detected) - Install Command:
npm install
Deployment triggers:
- Push to main → Production deployment
- Push to other branches → Preview deployment
- Pull requests → Preview deployment with unique URL
Database Connections
Vercel Postgres (managed):
# Create database
vercel postgres create
# Get connection string
vercel env add POSTGRES_URL
External database (Supabase, PlanetScale):
# Add connection string as environment variable
vercel env add DATABASE_URL
Custom Domains
# Add domain via CLI
vercel domains add yourdomain.com
# Or via dashboard: Settings → Domains
DNS Configuration:
- Add CNAME record:
www→cname.vercel-dns.com - Add A record:
@→76.76.21.21
Fly.io Deployment
Initial Setup
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Create app (generates fly.toml)
fly launch
# Deploy
fly deploy
fly.toml Configuration
app = "your-app-name"
primary_region = "iad" # or closest to your users
[build]
dockerfile = "Dockerfile"
[env]
PORT = "8080"
# Public env vars only
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
[[vm]]
memory = "256mb"
cpu_kind = "shared"
cpus = 1
Environment Variables
# Set secrets (encrypted)
fly secrets set DATABASE_URL=postgres://...
fly secrets set API_KEY=secret123
# List secrets
fly secrets list
# Remove secret
fly secrets unset API_KEY
Persistent Storage (Volumes)
# Create volume
fly volumes create data --size 1
# Add to fly.toml
[[mounts]]
source = "data"
destination = "/data"
Database on Fly.io
# Create Postgres cluster
fly postgres create
# Attach to app
fly postgres attach --app your-app-name your-postgres-app
Scaling
# Scale machine count
fly scale count 2
# Scale VM size
fly scale vm shared-cpu-2x
# Scale memory
fly scale memory 512
Deployments & Rollback
# Deploy
fly deploy
# View releases
fly releases
# Rollback to previous version
fly releases rollback <version>
Hostinger VPS Deployment
Initial VPS Setup
# SSH into VPS
ssh root@your-vps-ip
# Update system
apt update && apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Install Docker Compose
apt install docker-compose-plugin
# Verify
docker --version
docker compose version
Project Structure for VPS
your-app/
├── docker-compose.yml
├── Dockerfile (if custom)
├── .env (secrets, not in git)
├── nginx.conf (if using nginx)
└── your-app-files/
docker-compose.yml Example
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
env_file:
- .env
restart: unless-stopped
volumes:
- ./data:/app/data
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
Deployment Workflow
# On VPS: Clone or pull latest code
cd /root
git clone https://github.com/your-repo.git
cd your-repo
# Create .env file (never commit this!)
nano .env
# Add: DATABASE_URL=...
# API_KEY=...
# etc.
# Build and start
docker compose up -d --build
# View logs
docker compose logs -f
# Stop
docker compose down
# Update deployment
git pull
docker compose up -d --build
Zero-Downtime Updates
# Build new version
docker compose build app
# Scale up new instance
docker compose up -d --scale app=2
# Health check new instance
# (verify it's working)
# Remove old instance
docker compose up -d --scale app=1
# Or use docker-compose rolling updates
docker compose up -d --no-deps --build app
SSL/HTTPS Setup
Using Certbot (Let's Encrypt):
# Install certbot
apt install certbot python3-certbot-nginx
# Get certificate
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Auto-renewal (certbot installs cron job automatically)
certbot renew --dry-run
Monitoring & Health Checks
# Check running containers
docker ps
# View resource usage
docker stats
# Check logs
docker compose logs -f app
# Restart unhealthy container
docker compose restart app
Docker Best Practices
Dockerfile Optimization
# Use specific versions, not latest
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files first (better caching)
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build if needed
RUN npm run build
# Use non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1
# Start app
CMD ["npm", "start"]
.dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env*
.next
.DS_Store
README.md
docker-compose*.yml
Multi-Stage Builds
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
Environment Management
Environment Variable Hierarchy
1. Production (.env.production)
- DATABASE_URL
- API_KEYS
- SECRETS
2. Staging (.env.staging)
- Test database
- Sandbox API keys
3. Development (.env.local)
- Local database
- Test API keys
Never Commit Secrets
.gitignore must include:
.env
.env.local
.env.production
.env.staging
*.pem
*.key
secrets/
Secret Management Patterns
Option 1: Platform Environment Variables
- Vercel: Dashboard → Environment Variables
- Fly.io:
fly secrets set KEY=value - VPS:
.envfile on server (not in git)
Option 2: Secret Management Service
# Using Doppler (example)
doppler setup
doppler secrets set API_KEY=secret123
doppler run -- npm start
Option 3: Docker Secrets (Swarm)
# Create secret
echo "secret_value" | docker secret create db_password -
# Use in docker-compose.yml
services:
app:
secrets:
- db_password
Database Migrations
Pre-Deployment Checklist
- Backup database
- Test migration locally
- Test migration on staging
- Plan rollback procedure
- Schedule during low-traffic window (if possible)
Migration Strategies
Option 1: Automated (Prisma example)
# Generate migration
npx prisma migrate dev --name add_user_role
# Apply in production
npx prisma migrate deploy
Option 2: Manual SQL
# Connect to production database
psql $DATABASE_URL
# Run migration
\i migrations/001_add_column.sql
# Verify
\dt
SELECT * FROM users LIMIT 1;
Option 3: Zero-Downtime Migrations
-- Step 1: Add new column (nullable)
ALTER TABLE users ADD COLUMN new_field VARCHAR(255);
-- Step 2: Deploy code that writes to both old and new
-- (deploy application update)
-- Step 3: Backfill data
UPDATE users SET new_field = old_field WHERE new_field IS NULL;
-- Step 4: Make column NOT NULL
ALTER TABLE users ALTER COLUMN new_field SET NOT NULL;
-- Step 5: Deploy code that only uses new field
-- (deploy application update)
-- Step 6: Drop old column
ALTER TABLE users DROP COLUMN old_field;
Health Checks & Monitoring
Health Check Endpoint
// app/api/health/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
try {
// Check database connection
await prisma.$queryRaw`SELECT 1`
// Check external services
// await checkRedis()
// await checkS3()
return NextResponse.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
})
} catch (error) {
return NextResponse.json(
{ status: 'unhealthy', error: error.message },
{ status: 503 }
)
}
}
Monitoring Setup
Free Options:
- UptimeRobot: HTTP monitoring, free tier
- BetterUptime: Status pages, free tier
- Sentry: Error tracking, free tier
Configure monitoring:
# Uptime monitoring
# Add /api/health endpoint to UptimeRobot
# Configure alerts (email, Slack, etc.)
# Error tracking with Sentry
npm install @sentry/nextjs
npx @sentry/wizard -i nextjs
Logging Best Practices
// Use structured logging
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
})
logger.info({ userId: 123, action: 'login' }, 'User logged in')
logger.error({ error: err, context: 'payment' }, 'Payment failed')
Rollback Procedures
Vercel Rollback
# View deployments
vercel ls
# Rollback to previous deployment
vercel rollback [deployment-url]
# Or via dashboard: Deployments → ... → Promote to Production
Fly.io Rollback
# View release history
fly releases
# Rollback
fly releases rollback <version-number>
VPS Rollback
Using Git tags:
# Tag before deploying
git tag -a v1.2.3 -m "Production release 1.2.3"
git push origin v1.2.3
# Rollback to tag
git checkout v1.2.2
docker compose up -d --build
Using Docker image tags:
# Tag images before deploying
docker tag app:latest app:v1.2.3
# Rollback
docker compose stop app
docker run -d app:v1.2.2
Database rollback:
# Restore from backup
pg_restore -d mydb backup.dump
# Or run reverse migration
psql $DATABASE_URL < migrations/rollback/001_down.sql
Deployment Checklist
Before deploying:
- Code reviewed and tested
- Tests passing
- Environment variables configured
- Database migrations planned
- Backup of current database
- Rollback plan documented
- Health check endpoint working
- Monitoring configured
During deployment:
- Run database migrations
- Deploy application
- Verify health check passes
- Smoke test critical features
- Monitor error rates
- Check logs for issues
After deployment:
- Verify critical user flows
- Monitor performance metrics
- Check error tracking dashboard
- Communicate deployment to team
- Tag release in git
- Document any issues encountered
Common Issues & Solutions
| Issue | Solution |
|---|---|
| "Out of memory" on deploy | Increase VM size or optimize build process |
| Environment variables not working | Rebuild/restart after changing env vars |
| Database connection timeout | Check firewall rules, connection string |
| SSL certificate errors | Verify DNS propagation, renew certificate |
| "Port already in use" | Stop conflicting process or use different port |
| Slow cold starts | Increase min instances or use edge functions |
| Build failures | Check build logs, verify dependencies installed |
Platform-Specific Commands Reference
Vercel
vercel # Deploy to preview
vercel --prod # Deploy to production
vercel env ls # List environment variables
vercel logs # View logs
vercel domains ls # List domains
Fly.io
fly deploy # Deploy app
fly status # Check app status
fly logs # Stream logs
fly secrets list # List secrets
fly scale show # Show current scaling
fly ssh console # SSH into VM
Docker/VPS
docker compose up -d # Start services
docker compose down # Stop services
docker compose logs -f # Follow logs
docker compose ps # List containers
docker system prune -a # Clean up unused images
Resources
Documentation:
- Vercel: https://vercel.com/docs
- Fly.io: https://fly.io/docs
- Docker: https://docs.docker.com
Tools:
- GitHub Actions (CI/CD)
- Sentry (Error tracking)
- UptimeRobot (Monitoring)
- Doppler (Secret management)
Best Practices:
- 12-Factor App: https://12factor.net
- Docker Best Practices: https://docs.docker.com/develop/dev-best-practices
Deployments should be routine and stress-free. With proper environment management, health checks, and rollback procedures, you can deploy confidently and recover quickly when issues arise.