Skip to main content
This guide covers production deployment best practices, security hardening, monitoring, and operational procedures for running Budgetron at scale.

Production Checklist

Before deploying to production:
  • Set strong, unique secrets for AUTH_SECRET, CRON_SECRET_TOKEN
  • Configure SSL/TLS certificates (HTTPS)
  • Set up database backups
  • Configure monitoring and alerting
  • Review and restrict network access
  • Enable database connection pooling
  • Set up log aggregation
  • Configure rate limiting
  • Test disaster recovery procedures
  • Document deployment and rollback procedures

Security Hardening

Environment Variables

Never commit secrets to version control. Use secure secret management:

Generate Strong Secrets

# AUTH_SECRET (32+ bytes)
openssl rand -base64 32

# CRON_SECRET_TOKEN (hex format)
openssl rand -hex 32

# CRON_SECRET_SLUG
openssl rand -hex 16

Secret Management

Docker Secrets (Docker Swarm):
echo "your-secret" | docker secret create auth_secret -
services:
  app:
    secrets:
      - auth_secret
    environment:
      AUTH_SECRET_FILE: /run/secrets/auth_secret

secrets:
  auth_secret:
    external: true
Kubernetes Secrets:
kubectl create secret generic budgetron-secrets \
  --from-literal=AUTH_SECRET=$(openssl rand -base64 32) \
  --from-literal=DB_URL="postgres://..."
HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault for enterprise deployments.

Database Security

Connection Security

Use SSL for database connections:
DB_URL="postgres://user:pass@host:5432/db?sslmode=require"
SSL modes:
  • require - Requires SSL, no certificate verification
  • verify-ca - Requires SSL, verifies CA
  • verify-full - Requires SSL, verifies CA and hostname

Network Isolation

Restrict database access:
# PostgreSQL pg_hba.conf
host    budgetron    budgetron    10.0.0.0/8    scram-sha-256
Use private networks or VPC for database connections.

Database User Permissions

Create a limited database user:
-- Create user with minimal privileges
CREATE USER budgetron_app WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE budgetron TO budgetron_app;
GRANT USAGE ON SCHEMA public TO budgetron_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO budgetron_app;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO budgetron_app;

-- For migrations, use separate user
CREATE USER budgetron_migrate WITH PASSWORD 'different_password';
GRANT ALL PRIVILEGES ON DATABASE budgetron TO budgetron_migrate;

Application Security

Run as Non-Root User

Modify Dockerfile:
# Add before CMD
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
RUN chown -R nextjs:nodejs /app
USER nextjs

Read-Only Filesystem

Run container with read-only root:
docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /app/.next/cache \
  budgetron

Security Headers

Next.js automatically sets security headers. Verify in next.config.ts:
const nextConfig: NextConfig = {
  headers: async () => [
    {
      source: '/:path*',
      headers: [
        { key: 'X-DNS-Prefetch-Control', value: 'on' },
        { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
      ],
    },
  ],
}

Network Security

Firewall Configuration

Use UFW (Ubuntu):
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp   # SSH
sudo ufw allow 80/tcp   # HTTP
sudo ufw allow 443/tcp  # HTTPS
sudo ufw enable

Rate Limiting

Configure Nginx rate limiting:
http {
    limit_req_zone $binary_remote_addr zone=budgetron:10m rate=10r/s;
    
    server {
        location / {
            limit_req zone=budgetron burst=20 nodelay;
            proxy_pass http://localhost:3000;
        }
    }
}

DDoS Protection

Use Cloudflare or AWS WAF for DDoS protection and CDN caching.

High Availability

Load Balancing

Run multiple Budgetron instances behind a load balancer:
# docker-compose.yml
services:
  app:
    image: ghcr.io/budgetron-org/budgetron:latest
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure
    environment:
      DB_URL: ${DB_URL}
      # ... other env vars

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
Nginx upstream configuration:
upstream budgetron {
    least_conn;
    server app:3000 max_fails=3 fail_timeout=30s;
    server app:3000 max_fails=3 fail_timeout=30s;
    server app:3000 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    location / {
        proxy_pass http://budgetron;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
    }
}

Database High Availability

PostgreSQL Replication

Set up primary-replica replication:
# Primary
postgresql.conf:
  wal_level = replica
  max_wal_senders = 3
  
pg_hba.conf:
  host replication replicator 10.0.0.0/8 scram-sha-256

Managed Database Services

Use managed PostgreSQL for automatic failover:
  • AWS RDS with Multi-AZ
  • Google Cloud SQL with high availability
  • Azure Database for PostgreSQL with read replicas
  • DigitalOcean Managed Databases

Health Checks

Implement comprehensive health checks:
// app/api/health/route.ts
import { db } from '@/server/db'
import { NextResponse } from 'next/server'

export async function GET() {
  try {
    // Check database connectivity
    await db.execute('SELECT 1')
    
    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      database: 'connected',
    })
  } catch (error) {
    return NextResponse.json(
      {
        status: 'unhealthy',
        error: error.message,
      },
      { status: 503 }
    )
  }
}
Docker health check:
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => { \
    if (r.statusCode !== 200) process.exit(1); \
    let data = ''; \
    r.on('data', chunk => data += chunk); \
    r.on('end', () => { \
      const json = JSON.parse(data); \
      process.exit(json.status === 'healthy' ? 0 : 1); \
    }); \
  }).on('error', () => process.exit(1))"

Monitoring and Observability

Logging

Structured Logging

Budgetron logs to stdout/stderr. Use a log aggregation service: Docker Compose with Loki:
services:
  app:
    logging:
      driver: loki
      options:
        loki-url: "http://localhost:3100/loki/api/v1/push"
        loki-retries: 5
Centralized Logging Services:
  • Datadog
  • New Relic
  • Papertrail
  • Logtail
  • ELK Stack (Elasticsearch, Logstash, Kibana)

Log Rotation

For standalone deployments:
# /etc/logrotate.d/budgetron
/var/log/budgetron/*.log {
    daily
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 budgetron budgetron
    sharedscripts
    postrotate
        pm2 reloadLogs
    endscript
}

Metrics

Application Metrics

Integrate Prometheus metrics:
// lib/metrics.ts
import { Registry, Counter, Histogram } from 'prom-client'

export const register = new Registry()

export const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status'],
  registers: [register],
})

export const httpRequestTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status'],
  registers: [register],
})
// app/api/metrics/route.ts
import { register } from '@/lib/metrics'
import { NextResponse } from 'next/server'

export async function GET() {
  const metrics = await register.metrics()
  return new NextResponse(metrics, {
    headers: { 'Content-Type': register.contentType },
  })
}

Database Metrics

Monitor PostgreSQL:
-- Active connections
SELECT count(*) FROM pg_stat_activity WHERE datname = 'budgetron';

-- Database size
SELECT pg_size_pretty(pg_database_size('budgetron'));

-- Slow queries
SELECT query, calls, total_time, mean_time 
FROM pg_stat_statements 
ORDER BY mean_time DESC 
LIMIT 10;
Use monitoring tools:
  • pgAdmin
  • pganalyze
  • pg_stat_statements extension

Infrastructure Metrics

Prometheus + Grafana:
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin

Alerting

Health Check Monitoring

Use uptime monitoring:
  • UptimeRobot (free tier available)
  • Pingdom
  • Better Uptime
  • StatusCake

Custom Alerts

Prometheus alerting rules:
groups:
  - name: budgetron
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value }} errors/second"

      - alert: DatabaseConnectionFailed
        expr: up{job="postgres"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Database connection failed"

Backup and Recovery

Database Backups

Automated Backup Script

#!/bin/bash
# /usr/local/bin/backup-budgetron.sh

set -e

# Configuration
BACKUP_DIR="/var/backups/budgetron"
S3_BUCKET="s3://my-backups/budgetron"
DB_URL="postgres://user:pass@localhost:5432/budgetron"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/budgetron_$DATE.sql.gz"

# Create backup directory
mkdir -p $BACKUP_DIR

# Dump database
echo "Starting backup at $(date)"
pg_dump "$DB_URL" | gzip > "$BACKUP_FILE"

# Verify backup
if [ -f "$BACKUP_FILE" ] && [ $(stat -f%z "$BACKUP_FILE") -gt 1000 ]; then
    echo "Backup created successfully: $BACKUP_FILE"
else
    echo "ERROR: Backup failed or is too small"
    exit 1
fi

# Upload to S3 (optional)
if command -v aws &> /dev/null; then
    echo "Uploading to S3..."
    aws s3 cp "$BACKUP_FILE" "$S3_BUCKET/"
fi

# Remove old backups
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed at $(date)"
Make executable and schedule:
chmod +x /usr/local/bin/backup-budgetron.sh
sudo crontab -e
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-budgetron.sh >> /var/log/budgetron-backup.log 2>&1

Point-in-Time Recovery (PITR)

Enable WAL archiving in PostgreSQL:
# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f'
archive_timeout = 3600

Application State Backup

Budgetron is stateless, but back up:
  • Environment variables (securely)
  • Custom configurations
  • SSL certificates

Disaster Recovery

Recovery Procedure

  1. Restore Database:
gunzip -c budgetron_20260305_020000.sql.gz | psql "$DB_URL"
  1. Deploy Application:
docker run -d \
  --name budgetron \
  --env-file .env \
  -p 3000:3000 \
  ghcr.io/budgetron-org/budgetron:latest
  1. Verify:
curl http://localhost:3000/api/health

Test Recovery

Regularly test your recovery procedure:
# Test restoration to a separate database
createdb budgetron_test
gunzip -c latest_backup.sql.gz | psql budgetron_test

# Verify data integrity
psql budgetron_test -c "SELECT COUNT(*) FROM transactions;"

Performance Optimization

Database Optimization

Connection Pooling

Budgetron uses Drizzle ORM with connection pooling. Configure pool size:
// For high-traffic deployments
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DB_URL,
  max: 20,           // Maximum pool size
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
})

export const db = drizzle(pool)

Database Indexes

Ensure proper indexes exist:
-- Example: Index on user transactions
CREATE INDEX idx_transactions_user_date ON transactions(user_id, date DESC);
CREATE INDEX idx_transactions_category ON transactions(category_id);

Query Optimization

Monitor slow queries:
-- Enable pg_stat_statements
CREATE EXTENSION pg_stat_statements;

-- Find slow queries
SELECT 
  query,
  calls,
  total_exec_time,
  mean_exec_time,
  max_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 100  -- queries taking >100ms
ORDER BY mean_exec_time DESC
LIMIT 20;

Application Optimization

Caching

Next.js App Router includes built-in caching. Configure cache headers:
export const revalidate = 3600  // Revalidate every hour

CDN Integration

Use a CDN for static assets:
  • Cloudflare
  • AWS CloudFront
  • Vercel Edge Network

Resource Limits

Set container resource limits:
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G

Deployment Strategies

Blue-Green Deployment

# Deploy new version (green)
docker run -d --name budgetron-green \
  --env-file .env \
  -p 3001:3000 \
  ghcr.io/budgetron-org/budgetron:latest

# Test green deployment
curl http://localhost:3001/api/health

# Switch traffic (update load balancer)
# If successful, remove blue
docker stop budgetron-blue
docker rm budgetron-blue

Rolling Updates

With Docker Swarm or Kubernetes:
docker service update \
  --image ghcr.io/budgetron-org/budgetron:v0.2.0 \
  --update-parallelism 1 \
  --update-delay 30s \
  budgetron

Rollback Procedure

# Docker
docker stop budgetron
docker run -d --name budgetron \
  --env-file .env \
  -p 3000:3000 \
  ghcr.io/budgetron-org/budgetron:v0.1.0  # Previous version

# Kubernetes
kubectl rollout undo deployment/budgetron

Compliance and Auditing

Audit Logging

Log all authentication and sensitive operations:
import { logger } from '@/lib/logger'

// Log authentication events
logger.info('User login', {
  userId: user.id,
  ip: request.ip,
  timestamp: new Date().toISOString(),
})

Data Privacy

  • Implement GDPR data export/deletion
  • Encrypt sensitive data at rest
  • Use TLS 1.3 for data in transit
  • Regular security audits

Compliance Checklist

  • Data encryption (at rest and in transit)
  • User data export functionality
  • User data deletion functionality
  • Audit logging
  • Access controls
  • Regular backups
  • Incident response plan

Next Steps

Build docs developers (and LLMs) love