Deployment

Guide to deploying BrewHoard to production, including configuration, environment setup, and monitoring.

This guide covers deploying BrewHoard to production environments, including configuration best practices and monitoring setup.

Deployment Options

BrewHoard can be deployed to various platforms:

PlatformBest ForConsiderations
VercelSimplest deploymentNative SvelteKit support
Cloudflare PagesEdge performanceWorkers adapter needed
RailwayFull-stack with DBManaged PostgreSQL
DockerSelf-hostedFull control
Node.js ServerTraditional hostingManual setup

Environment Variables

Required Variables

Bash
# Database
DATABASE_URL="postgres://user:pass@host:5432/brewhoard"

# Session security (generate with: openssl rand -hex 32)
SESSION_SECRET="your-64-character-hex-secret"

# Application
PUBLIC_APP_URL="https://brewhoard.com"
NODE_ENV="production"

Optional Variables

Bash
# Vision API (for beer scanner)
VISION_API_KEY="your-vision-api-key"

# Email (SMTP)
SMTP_HOST="smtp.sendgrid.net"
SMTP_PORT="587"
SMTP_USER="apikey"
SMTP_PASS="your-api-key"
SMTP_FROM="noreply@brewhoard.com"

# File storage (S3-compatible)
S3_BUCKET="brewhoard-uploads"
S3_REGION="us-east-1"
S3_ACCESS_KEY="AKIA..."
S3_SECRET_KEY="..."
S3_ENDPOINT="https://s3.amazonaws.com"

# Redis (for sessions/caching)
REDIS_URL="redis://localhost:6379"

# Analytics
PLAUSIBLE_DOMAIN="brewhoard.com"

Database Setup

Production PostgreSQL

Bash
# Create database
createdb brewhoard_prod

# Run migrations
DATABASE_URL="postgres://..." npm run migrate

Connection Pooling

For production, use connection pooling:

JavaScript
// src/lib/server/db.js
import postgres from 'postgres';

const sql = postgres(process.env.DATABASE_URL, {
  max: 20,                    // Max connections
  idle_timeout: 20,           // Close idle after 20s
  connect_timeout: 10,        // Connection timeout
  ssl: process.env.NODE_ENV === 'production' ? 'require' : false,
  prepare: false              // Required for some poolers
});

export default sql;

Backup Strategy

Bash
# Daily backup script
#!/bin/bash
DATE=$(date +%Y%m%d)
pg_dump $DATABASE_URL | gzip > /backups/brewhoard_$DATE.sql.gz

# Keep last 30 days
find /backups -name "brewhoard_*.sql.gz" -mtime +30 -delete

Build Configuration

svelte.config.js for Production

JavaScript
import adapter from '@sveltejs/adapter-node';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      out: 'build',
      precompress: true,
      envPrefix: ''
    })
  }
};

export default config;

Production Build

Bash
# Build for production
npm run build

# Preview production build locally
npm run preview

PWA Configuration

Service Worker

JavaScript
// src/service-worker.js
import { build, files, version } from '$service-worker';

const CACHE = `cache-${version}`;
const ASSETS = [...build, ...files];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => 
      Promise.all(
        keys.filter(key => key !== CACHE).map(key => caches.delete(key))
      )
    )
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;
  
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    })
  );
});

Web Manifest

JSON
// static/manifest.json
{
  "name": "BrewHoard",
  "short_name": "BrewHoard",
  "description": "Manage your beer collection",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#f59e0b",
  "icons": [
    {
      "src": "/pwa-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/pwa-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Security Checklist

Before Go-Live

  • All secrets in environment variables (not in code)
  • SESSION_SECRET is unique and secure (64+ chars)
  • HTTPS enforced (redirect HTTP)
  • Database SSL enabled
  • CORS configured correctly
  • Rate limiting enabled
  • CSP headers configured
  • Input validation on all endpoints
  • SQL injection protection (parameterized queries)
  • XSS protection (content sanitization)
  • File upload restrictions (type, size)

Security Headers

JavaScript
// src/hooks.server.js
export async function handle({ event, resolve }) {
  const response = await resolve(event);
  
  // Security headers
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  response.headers.set('Permissions-Policy', 'camera=(self), microphone=()');
  
  return response;
}

Monitoring

Health Check Endpoint

JavaScript
// src/routes/api/v1/health/+server.js
import sql from '$lib/server/db.js';

export async function GET() {
  const checks = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    services: {}
  };
  
  // Check database
  try {
    await sql`SELECT 1`;
    checks.services.database = 'ok';
  } catch {
    checks.services.database = 'error';
    checks.status = 'degraded';
  }
  
  return json(checks, {
    status: checks.status === 'ok' ? 200 : 503
  });
}

Error Logging

JavaScript
// src/hooks.server.js
import { dev } from '$app/environment';

export async function handleError({ error, event }) {
  const errorId = crypto.randomUUID();
  
  // Log error details
  console.error({
    id: errorId,
    url: event.url.pathname,
    method: event.request.method,
    error: error.message,
    stack: error.stack
  });
  
  // In production, send to error tracking service
  if (!dev) {
    // await sendToSentry(error, { id: errorId, event });
  }
  
  return {
    message: 'An unexpected error occurred',
    id: errorId
  };
}

Performance Optimization

Caching Headers

JavaScript
// src/routes/api/v1/beers/+server.js
export async function GET({ setHeaders }) {
  const beers = await getBeers();
  
  // Cache for 5 minutes
  setHeaders({
    'Cache-Control': 'public, max-age=300'
  });
  
  return json({ data: beers });
}

Database Query Optimization

SQL
-- Add indexes for common queries
CREATE INDEX CONCURRENTLY idx_collection_user_updated 
  ON user_collection(user_id, updated_at DESC);

-- Analyze query performance
EXPLAIN ANALYZE SELECT ...

Rollback Procedure

If deployment fails:

Bash
# Vercel: Instant rollback
vercel rollback

# Docker: Rollback to previous image
docker-compose down
docker tag brewhoard:current brewhoard:failed
docker tag brewhoard:previous brewhoard:current
docker-compose up -d

# Database: Restore from backup
pg_restore -d brewhoard_prod /backups/brewhoard_YYYYMMDD.sql.gz

Next Steps