Docker Compose

Run the full Hyperbolic stack — API server, web app, Postgres — on your own infrastructure with one command.

Hyperbolic is designed to self-host cleanly. The public beta at hyperbolic.sh runs on Railway + Vercel + Neon, but everything you see there is fully reproducible on your own hardware.

Prerequisites#

  • Node.js ≥ 20
  • pnpm ≥ 9
  • PostgreSQL ≥ 15 (Neon, Supabase, or self-hosted)
  • Docker & Docker Compose (recommended)

Quickstart#

git clone https://github.com/jdgmnt33/hyperbolic
cd hyperbolic
cp .env.production.example .env
# edit .env — see the env page
 
docker compose up -d --build
 
curl http://localhost:3111/api/health

The compose file exposes:

  • API server on ${PORT:-3111}
  • Web dashboard on ${WEB_PORT:-3222}

Both run as non-root user appuser (UID 1001).

Updating#

git pull
docker compose up -d --build

Database schema#

First-time setup requires pushing the Drizzle schema:

cd apps/server
DATABASE_URL="postgres://…" npx drizzle-kit push

Re-run this whenever you upgrade to a version that adds tables or columns.

Direct Node deployment#

If you prefer no Docker:

pnpm install
pnpm build
 
# Schema push (first deploy only)
cd apps/server && DATABASE_URL="…" npx drizzle-kit push && cd ../..
 
# Start both processes (or use your process manager of choice)
cd apps/server && node dist/index.js &
cd apps/web && node .next/standalone/server.js &

Reverse proxy#

SSE requires careful proxy configuration. The short version: disable response buffering on the API path.

Caddy#

api.example.com {
  reverse_proxy localhost:3111 {
    flush_interval -1
  }
}
app.example.com {
  reverse_proxy localhost:3222
}

nginx#

server {
  server_name api.example.com;
  location / {
    proxy_pass http://127.0.0.1:3111;
    proxy_set_header Host $host;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
    proxy_http_version 1.1;
    chunked_transfer_encoding off;
  }
  listen 443 ssl;
}

Cloudflare#

If you proxy through Cloudflare, enable "Grey cloud" (DNS-only) for the API subdomain, or the streaming connection will be buffered. The web subdomain is fine under the orange cloud.

Health and monitoring#

| Endpoint | Purpose | |---|---| | GET /api/health | Server health, uptime, active SSE count | | GET /api/ping | Lightweight connectivity check | | /health (web) | Dashboard health page |

Point an uptime monitor (UptimeRobot, Better Stack, etc.) at /api/health.

Common pitfalls#

SSE buffering

nginx, Cloudflare, and many CDNs buffer streaming responses by default. If SSE "appears stuck", disable buffering on the API path.

NEXT_PUBLIC_ vars are build-time

Changing NEXT_PUBLIC_API_URL or NEXT_PUBLIC_SSE_URL requires rebuilding the web image. They can't be changed at runtime.

CORS origin strictness

CORS_ORIGINS must exactly match the browser's origin (protocol + host + port). No trailing slashes.