HelloJohn / docs
Self-Hosting

Scaling

Scale HelloJohn horizontally, configure Redis for distributed state, and tune for high-traffic workloads.

Scaling

HelloJohn is designed to scale horizontally. Multiple instances can run behind a load balancer with no shared in-memory state — all state lives in PostgreSQL and Redis.


Horizontal Scaling

Run multiple HelloJohn instances behind any load balancer:

         ┌─────────────────────────┐
         │    Load Balancer        │
         │  (nginx / ALB / Caddy)  │
         └──────┬──────────┬───────┘
                │          │
        ┌───────▼──┐  ┌────▼──────┐
        │ HJ Pod 1 │  │ HJ Pod 2  │
        └───────┬──┘  └────┬──────┘
                │          │
         ┌──────▼──────────▼──────┐
         │     PostgreSQL         │
         └──────────┬─────────────┘

         ┌──────────▼─────────────┐
         │     Redis (optional)   │
         └────────────────────────┘

No sticky sessions required. HelloJohn is fully stateless — any instance can handle any request.


Redis Requirement for Multi-Instance

When running multiple instances, Redis is required for:

FeatureWithout RedisWith Redis
Rate limitingPer-instance (inconsistent)Distributed (consistent)
Session cacheNoneShared cache across instances
Webhook event dedupNot supportedSupported
HELLOJOHN_REDIS_URL=redis://user:password@redis-host:6379/0

For Redis Cluster:

HELLOJOHN_REDIS_URL=redis+cluster://node1:6379,node2:6379,node3:6379/0

Database Connection Pooling

At scale, many HelloJohn instances each maintaining their own PostgreSQL connection pool can exhaust database connections. Use PgBouncer in transaction mode as a connection pooler:

PgBouncer (Docker)

# docker-compose addition
pgbouncer:
  image: pgbouncer/pgbouncer
  environment:
    DATABASES_HOST: postgres
    DATABASES_PORT: 5432
    DATABASES_USER: hellojohn
    DATABASES_PASSWORD: your_password
    DATABASES_DBNAME: hellojohn
    POOL_MODE: transaction
    MAX_CLIENT_CONN: 1000
    DEFAULT_POOL_SIZE: 25
  ports:
    - "6432:5432"

Update HelloJohn to connect through PgBouncer:

HELLOJOHN_DATABASE_URL=postgresql://hellojohn:password@pgbouncer:6432/hellojohn?sslmode=require

Managed Connection Pooling

Most managed PostgreSQL providers offer built-in pooling:

  • Neon: Connection pooling enabled by default (Supavisor)
  • Supabase: Use the pooler connection string
  • RDS Proxy: AWS-native connection pooler

Load Balancer Configuration

nginx Upstream

upstream hellojohn {
  least_conn;
  server hj-instance-1:8080;
  server hj-instance-2:8080;
  server hj-instance-3:8080;

  keepalive 32;
}

server {
  listen 443 ssl;
  server_name auth.example.com;

  location / {
    proxy_pass http://hellojohn;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_connect_timeout 5s;
    proxy_read_timeout 30s;
  }
}

AWS Application Load Balancer

  • Target type: IP (for ECS) or Instance (for EC2)
  • Stickiness: Disabled (HelloJohn is stateless)
  • Health check path: GET /health
  • Deregistration delay: 30s (matches HelloJohn's request timeout)

Cloudflare

If using Cloudflare as a reverse proxy:

  • Enable HTTP/2 and HTTP/3
  • Set Cache Everything to OFF for the HelloJohn domain
  • Use Argo Smart Routing for multi-region performance

Autoscaling

Docker Swarm

services:
  hellojohn:
    image: ghcr.io/hellojohn/hellojohn:latest
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
      restart_policy:
        condition: on-failure

Kubernetes HPA

See Kubernetes Deployment for the full HPA manifest.

spec:
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Performance Tuning

HelloJohn Configuration

# Worker goroutines (default: 4x CPU count)
HELLOJOHN_WORKERS=16

# Database connection pool per instance
HELLOJOHN_DB_MAX_OPEN_CONNS=25
HELLOJOHN_DB_MAX_IDLE_CONNS=10
HELLOJOHN_DB_CONN_MAX_LIFETIME=5m

# HTTP timeouts
HELLOJOHN_READ_TIMEOUT=10s
HELLOJOHN_WRITE_TIMEOUT=30s
HELLOJOHN_IDLE_TIMEOUT=120s

PostgreSQL Tuning

For a dedicated PostgreSQL server handling HelloJohn at scale:

# postgresql.conf
max_connections = 200
shared_buffers = 256MB          # 25% of RAM
effective_cache_size = 768MB    # 75% of RAM
work_mem = 4MB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1          # For SSD storage

Capacity Planning

Approximate resource usage per HelloJohn instance:

MetricLight LoadMedium LoadHeavy Load
CPU< 50m100-200m300-500m
Memory64 MB128 MB256 MB
DB connections5-1015-2020-25
Requests/sec< 5050-500500-2000

Rule of thumb: 1 HelloJohn instance handles ~500 sign-in requests per second on modern hardware. Scale horizontally beyond that.

MAU to Instance Count

MAUTypical InstancesNotes
< 10K1-2Single server fine
10K–100K2-4Add Redis
100K–1M4-10Managed DB recommended
1M+10+Multi-region, global DB

Monitoring

Set up monitoring to detect performance issues:

Key Metrics to Track

MetricAlert Threshold
API p99 latency> 500ms
Error rate> 1%
Database connection pool saturation> 80%
Redis memory usage> 80%
CPU utilization> 80% sustained

Prometheus Metrics

HelloJohn exposes Prometheus metrics at GET /metrics:

# Scrape config for prometheus.yml
scrape_configs:
  - job_name: hellojohn
    static_configs:
      - targets:
          - hj-instance-1:8080
          - hj-instance-2:8080
    metrics_path: /metrics

Key metrics exposed:

hellojohn_http_requests_total{method, path, status}
hellojohn_http_request_duration_seconds{method, path, quantile}
hellojohn_db_pool_connections{state}
hellojohn_auth_sign_ins_total{method, status}
hellojohn_webhook_deliveries_total{status}

Zero-Downtime Deploys

HelloJohn supports zero-downtime rolling updates:

  1. Migrations must be backward-compatible (always additive, never drop columns used by old version)
  2. Run migrations before updating instances: hj migrate
  3. Rolling restart: replace instances one at a time
  4. Kubernetes handles this automatically with the rolling update strategy
# Manual rolling restart (Kubernetes)
kubectl rollout restart deployment/hellojohn -n hellojohn

# Watch rollout status
kubectl rollout status deployment/hellojohn -n hellojohn

On this page