Self-Hosting Guide
HPI can be self-hosted on your own infrastructure. It requires two backing services: an S3-compatible object store and a database (SQLite or PostgreSQL). Optionally, you can add NATS JetStream for external integrations (event streaming, cancel requests, NATS notification sinks).
Prerequisites
- Node.js 20+ or Deno 2+
- Docker (for backing services, or use managed equivalents)
Infrastructure
S3-Compatible Object Storage
Any S3-compatible service works: AWS S3, MinIO, RustFS, Cloudflare R2, etc. The app auto-creates the bucket on first file upload.
docker run -d \
--name hpi-s3 \
-p 9000:9000 \
-p 9001:9001 \
-v hpi-s3-data:/data \
-e RUSTFS_ACCESS_KEY=minioadmin \
-e RUSTFS_SECRET_KEY=minioadmin \
--restart unless-stopped \
rustfs/rustfs:latest
Database
SQLite (default) requires no additional setup — the database file is created automatically. For production deployments with multiple app instances, use PostgreSQL.
Environment Variables
Required
| Variable | Description |
|---|---|
BETTER_AUTH_SECRET | Secret key for session signing. Generate a strong random string. |
Application
| Variable | Default | Description |
|---|---|---|
MANAGED_CLOUD | — | Only set to true on the official cloud platform. Self-hosted deployments should leave this unset. |
PUBLIC_BASE_URL | http://localhost:5173 | Public URL where the app is accessible. Used for task links and emails. |
ORIGIN | Falls back to PUBLIC_BASE_URL | Origin URL for auth redirects and CORS |
Database
| Variable | Default | Description |
|---|---|---|
DB_BACKEND | sqlite | sqlite or postgres |
AUTH_DB_PATH | ./data/auth.db | Path to SQLite file (SQLite only) |
DATABASE_URL | — | PostgreSQL connection string (PostgreSQL only, required) |
Object Storage
| Variable | Default | Description |
|---|---|---|
S3_ENDPOINT | http://localhost:9000 | S3-compatible endpoint URL |
S3_BUCKET | human-tasks | Bucket name |
S3_ACCESS_KEY | minioadmin | Access key |
S3_SECRET_KEY | minioadmin | Secret key |
S3_REGION | us-east-1 | AWS region (ignored by most S3-compatible services) |
S3_PUBLIC_URL | — | Public URL for direct file access. If unset, files are served through the app. |
NATS (Optional)
NATS JetStream enables external integrations: event streaming for external subscribers, inbound cancel requests, and NATS notification sinks. The app runs fully standalone without it.
| Variable | Default | Description |
|---|---|---|
NATS_URL | — | NATS connection URL (e.g. nats://localhost:4222). Leave unset to run without NATS. |
To enable NATS, start a NATS server with JetStream enabled:
docker run -d \
--name hpi-nats \
-p 4222:4222 \
-p 8222:8222 \
--restart unless-stopped \
nats:2.10-alpine -js
The app auto-creates six JetStream streams on startup (HUMAN_REQUESTS, HUMAN_COMPLETED, HUMAN_CANCEL, HUMAN_CANCELLED, HUMAN_FAILED, HUMAN_PROCESS) with 7-day retention.
Admin User
| Variable | Default | Description |
|---|---|---|
ADMIN_EMAIL | — | Seed an admin user on first startup |
ADMIN_PASSWORD | — | Password for the admin user |
ADMIN_NAME | Administrator | Display name |
Email (Optional)
Email is required for password resets, email verification, and invitation notifications. If not configured, these actions log to the console instead.
| Variable | Default | Description |
|---|---|---|
EMAIL_PROVIDER | smtp | smtp or brevo |
SMTP_HOST | — | SMTP server hostname |
SMTP_PORT | 587 | SMTP port |
SMTP_SECURE | false | Set to true for TLS |
SMTP_USER | — | SMTP username |
SMTP_PASS | — | SMTP password |
SMTP_FROM | HPI <noreply@example.com> | Sender address |
BREVO_API_KEY | — | Brevo API key (when using Brevo provider) |
Quick Start
1. Start backing services
docker run -d --name hpi-s3 -p 9000:9000 -v hpi-s3-data:/data \
-e RUSTFS_ACCESS_KEY=minioadmin -e RUSTFS_SECRET_KEY=minioadmin \
--restart unless-stopped rustfs/rustfs:latest
2. Configure environment
Create a .env file in the project root:
BETTER_AUTH_SECRET=change-me-to-a-random-string
PUBLIC_BASE_URL=http://localhost:3000
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-me
3. Build and run
npm install
npm run build
node build/index.js
Or with Deno:
deno install
deno task build
deno run -A build/index.js
The app will start, run database migrations, seed the admin user, and connect to S3. If NATS_URL is set, it will also connect to NATS.
Docker Compose
A complete stack for production-like deployments:
services:
# Optional: uncomment to enable NATS for external integrations
# nats:
# image: nats:2.10-alpine
# command: ["-js", "-m", "8222"]
# ports:
# - "4222:4222"
# volumes:
# - nats_data:/data
# restart: unless-stopped
s3:
image: rustfs/rustfs:latest
environment:
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY:-minioadmin}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY:-minioadmin}
ports:
- '9000:9000'
volumes:
- s3_data:/data
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: human
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
POSTGRES_DB: hpi
volumes:
- db_data:/var/lib/postgresql/data
restart: unless-stopped
app:
build: .
environment:
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
DB_BACKEND: postgres
DATABASE_URL: postgresql://human:${DB_PASSWORD:-changeme}@postgres:5432/hpi
S3_ENDPOINT: http://s3:9000
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-minioadmin}
S3_SECRET_KEY: ${S3_SECRET_KEY:-minioadmin}
# NATS_URL: nats://nats:4222 # Uncomment to enable NATS
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:3000}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
ports:
- '3000:8000'
depends_on:
# - nats # Uncomment if using NATS
- s3
- postgres
restart: unless-stopped
volumes:
# nats_data: # Uncomment if using NATS
s3_data:
db_data:
Production Considerations
Database — Use PostgreSQL for multi-instance deployments. SQLite works well for single-instance setups but does not support concurrent writes from multiple processes.
NATS — NATS is optional. Enable it by setting NATS_URL when you need external event streaming, inbound cancel requests via NATS, or NATS notification sinks. All subjects use the human.* prefix (e.g. human.request.>, human.completed.>).
File storage — Set S3_PUBLIC_URL to serve uploaded files directly from your CDN or S3 endpoint rather than proxying through the app.
Email — Configure an email provider for password resets and invitations. Without email, these features fall back to console logging.
HTTPS — Place the app behind a reverse proxy (nginx, Caddy, Traefik) that handles TLS termination. Set PUBLIC_BASE_URL to your https:// domain.
Scaling — The app is stateless. Run multiple instances behind a load balancer. All coordination happens through the shared database.