Skip to main content

Overview

The production configuration is optimized for production deployments with multi-stage builds, resource limits, restart policies, and security best practices.

Production Features

Optimized Builds

Multi-stage Docker builds for smaller images

Resource Limits

Memory limits and CPU constraints

Auto-restart

Automatic restart on failure

Security Hardened

Strong passwords and security best practices

Prerequisites

  • Docker and Docker Compose installed
  • PostgreSQL database configured
  • GitHub OAuth app created
  • Resend API key for emails
  • Strong passwords generated

Environment Configuration

Always use strong, production-ready values. Never use development defaults in production.
Create a .env file with production values:
# Database
DATABASE_URL=postgresql://user:password@host:5432/database

# Auth (REQUIRED - use strong secret)
JWT_SECRET=your-strong-jwt-secret-here

# GitHub OAuth
CLIENT_ID_GITHUB=your-github-client-id
CLIENT_SECRET_GITHUB=your-github-client-secret

# Email (Resend)
RESEND_API_KEY=your-resend-api-key

# Redis Configuration (REQUIRED - use strong password)
# IMPORTANT: Use 127.0.0.1 for PM2 services running on host
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_USERNAME=
REDIS_PASSWORD=your-strong-redis-password  # REQUIRED

# Backend
BACKEND_PORT=8084

# Frontend
FRONTEND_PORT=3000
NEXT_PUBLIC_BACKEND_URL=http://your-backend-url:8084

# Worker Configuration
REGION_ID=us-east-1
WORKER_ID=worker-1

# ClickHouse (optional)
CLICKHOUSE_URL=
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=
CLICKHOUSE_DATABASE=default
CLICKHOUSE_METRICS_TABLE=uptime_checks
Generate a strong JWT secret using: openssl rand -base64 32

Quick Start

1

Navigate to Docker directory

cd docker
2

Build and start services

docker compose -f docker-compose.prod.yaml up --build -d
The -d flag runs services in detached mode (background).
3

Verify services are running

docker compose -f docker-compose.prod.yaml ps
All services should show as “Up” or “healthy”.
4

Check logs

docker compose -f docker-compose.prod.yaml logs -f
Monitor logs for any errors or issues.

Service Details

Redis

Image
string
default:"redis:7-alpine"
Lightweight Alpine-based Redis 7 image
Port
number
default:"6379"
Exposed to host for external connections
Volume
string
default:"redis-data"
Persistent storage for Redis data
Configuration:
  • Password protected (via REDIS_PASSWORD)
  • AOF (Append Only File) persistence enabled
  • Memory limit: 256MB with LRU eviction policy
  • Health checks configured

Backend

Runtime
string
default:"Bun"
High-performance JavaScript runtime
Port
number
default:"8084"
Configurable via BACKEND_PORT
Features:
  • Production-optimized build
  • tRPC API endpoints
  • Depends on Redis health check
  • Auto-restart on failure

Frontend

Runtime
string
default:"Node.js"
Next.js application
Port
number
default:"3000"
Configurable via FRONTEND_PORT
Features:
  • Standalone build for smaller image size
  • Production-optimized
  • Depends on Backend service

Worker

Runtime
string
default:"Bun"
Processes uptime checks from Redis streams
Features:
  • Consumes messages from Redis streams
  • Performs uptime checks
  • Reports results to ClickHouse (optional)
  • Auto-restart on failure

Publisher

Runtime
string
default:"Bun"
Publishes website checks to Redis streams
Features:
  • Publishes checks every 3 minutes
  • Reads active websites from database
  • Publishes to Redis streams for workers

Production Commands

Service Management

# Start services
docker compose -f docker-compose.prod.yaml up -d

# Stop services
docker compose -f docker-compose.prod.yaml down

# Restart all services
docker compose -f docker-compose.prod.yaml restart

# Restart specific service
docker compose -f docker-compose.prod.yaml restart backend

Monitoring

# View all logs
docker compose -f docker-compose.prod.yaml logs -f

# View logs for specific service
docker compose -f docker-compose.prod.yaml logs -f worker

# Check service status
docker compose -f docker-compose.prod.yaml ps

# View resource usage
docker stats

Updates and Rebuilds

# Rebuild all services
docker compose -f docker-compose.prod.yaml up --build -d

# Rebuild specific service
docker compose -f docker-compose.prod.yaml up --build -d backend

# Pull latest base images
docker compose -f docker-compose.prod.yaml pull

Security Best Practices

Always set a strong REDIS_PASSWORD in production.
Generate a secure password:
openssl rand -base64 32
Set in .env:
REDIS_PASSWORD=your-generated-password
Use a cryptographically secure random string:
openssl rand -base64 32
Never use the development default (dev-secret) in production.
  • Never commit .env files to version control
  • Use environment-specific .env files
  • Rotate secrets regularly
  • Use secrets management for sensitive values
  • Services are isolated in Docker networks
  • Only necessary ports are exposed to host
  • Consider using a reverse proxy (nginx, Traefik)
  • Enable HTTPS with SSL/TLS certificates
  • Update base images regularly for security patches
  • Monitor for vulnerabilities in dependencies
  • Keep Docker and Docker Compose up to date

Scaling

Scale services to handle increased load:
# Scale worker instances to 3
docker compose -f docker-compose.prod.yaml up -d --scale worker=3

# Scale publisher (usually only need 1)
docker compose -f docker-compose.prod.yaml up -d --scale publisher=1
Multiple workers will share the same consumer group, distributing load across instances.

Maintenance

Redis Data Backup

# Create backup
docker compose -f docker-compose.prod.yaml exec redis redis-cli \n  -a $REDIS_PASSWORD --rdb /data/dump.rdb

# Copy backup from container
docker cp better-uptime-redis:/data/dump.rdb \n  ./redis-backup-$(date +%Y%m%d).rdb

Redis Data Restore

# Copy backup to container
docker cp ./redis-backup.rdb better-uptime-redis:/data/dump.rdb

# Restart Redis to load backup
docker compose -f docker-compose.prod.yaml restart redis

Redis CLI Access

# Connect to Redis CLI
docker compose -f docker-compose.prod.yaml exec redis \n  redis-cli -a $REDIS_PASSWORD

# View Redis info
docker compose -f docker-compose.prod.yaml exec redis \n  redis-cli -a $REDIS_PASSWORD info

Migration from Cloud Redis

When migrating from cloud Redis to self-hosted:
1

Prepare for migration

Ensure all pending messages are processed in cloud Redis.
2

Update environment

Change REDIS_HOST from cloud URL to redis (for Docker networking) or 127.0.0.1 (for host networking).
3

Deploy Docker services

docker compose -f docker-compose.prod.yaml up --build -d
4

Verify connections

Check logs to ensure services connect successfully:
docker compose -f docker-compose.prod.yaml logs -f
5

Monitor

Watch for connection errors or message processing issues over the next few hours.

Troubleshooting

  1. Check service logs:
    docker compose -f docker-compose.prod.yaml logs service-name
    
  2. Verify environment variables are set correctly
  3. Check for port conflicts:
    netstat -tulpn | grep :8084
    
  4. Ensure Docker has enough resources (memory, disk space)
  1. Verify Redis password matches in all service configurations
  2. Check Redis is healthy:
    docker compose -f docker-compose.prod.yaml ps redis
    
  3. Test Redis connection:
    docker compose -f docker-compose.prod.yaml exec redis redis-cli -a $REDIS_PASSWORD ping
    
  1. Check Docker stats:
    docker stats
    
  2. Redis has a 256MB limit with LRU eviction
  3. Consider increasing Redis memory limit in docker-compose.prod.yaml:
    --maxmemory 512mb
    

Next Steps

Environment Variables

Complete environment variable reference

Database Setup

Configure PostgreSQL and run migrations

Development Setup

Set up local development environment

Build docs developers (and LLMs) love