HelloJohn / docs
Self-Hosting

Docker Setup

Run HelloJohn with Docker and Docker Compose on any server or local machine.

Docker Setup

The easiest way to self-host HelloJohn is with Docker Compose. A single file starts HelloJohn, PostgreSQL, and Redis together.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2
  • A domain pointing to your server (for production)

Minimal docker-compose.yml

version: "3.9"

services:
  hellojohn:
    image: ghcr.io/hellojohn/hellojohn:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://hellojohn:secret@postgres:5432/hellojohn
      JWT_SECRET: change-me-to-a-random-32-char-string
      APP_URL: http://localhost:3000
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: hellojohn
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: hellojohn
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U hellojohn"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Start it:

docker compose up -d
docker compose logs -f hellojohn

HelloJohn runs migrations automatically on first start.

Production docker-compose.yml

For a production deployment add Redis and proper secrets management:

version: "3.9"

services:
  hellojohn:
    image: ghcr.io/hellojohn/hellojohn:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"   # bind to localhost only — proxy in front
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file: .env.postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --save 60 1 --loglevel warning
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  postgres_data:
  redis_data:

.env file:

DATABASE_URL=postgres://hellojohn:strongpassword@postgres:5432/hellojohn
REDIS_URL=redis://redis:6379
JWT_SECRET=<generate with: openssl rand -hex 32>
APP_URL=https://auth.yourdomain.com
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USER=noreply@yourdomain.com
SMTP_PASS=<smtp-password>
SMTP_FROM=HelloJohn <noreply@yourdomain.com>

.env.postgres:

POSTGRES_USER=hellojohn
POSTGRES_PASSWORD=strongpassword
POSTGRES_DB=hellojohn

Using a managed database

If you use a managed PostgreSQL service (Supabase, Neon, RDS, etc.) remove the postgres service from docker-compose.yml and set DATABASE_URL directly:

DATABASE_URL=postgres://user:pass@db.projectref.supabase.co:5432/postgres?sslmode=require

Pinning a version

Use a specific version tag instead of latest in production:

image: ghcr.io/hellojohn/hellojohn:1.2.3

Available tags are listed at github.com/hellojohn/hellojohn/releases.

Common Docker commands

# View logs
docker compose logs -f hellojohn

# Restart after config change
docker compose up -d --force-recreate hellojohn

# Run a database migration manually
docker compose exec hellojohn hellojohn migrate

# Open a Postgres shell
docker compose exec postgres psql -U hellojohn

# Pull the latest image
docker compose pull
docker compose up -d

Image variants

TagDescription
latestLatest stable release
1.2.3Specific version (recommended for prod)
edgeBuilt from main branch — unstable

All images are multi-arch (linux/amd64, linux/arm64) and published to GitHub Container Registry.

On this page