The self-host bundle is a single Docker image that contains the API and the dashboard. The API serves the dashboard assets on the same origin, so there is only one container to run and no CORS configuration to manage.
Image: ghcr.io/ferrlabs/ferrflow-selfhost:latest
Prerequisites
- Docker 24+ and Docker Compose
- A PostgreSQL 16 database with the TimescaleDB extension (the reference compose file below provisions one for you)
Quickstart with Docker Compose
Download the reference compose file and environment template from the Application repository:
curl -O https://raw.githubusercontent.com/FerrLabs/Application/main/docker-compose.selfhost.yml
curl -o .env https://raw.githubusercontent.com/FerrLabs/Application/main/.env.example.selfhost
Generate secrets and edit .env:
# Database password (any long random string)
echo "DB_PASSWORD=$(openssl rand -base64 32)" >> .env.secrets
# JWT signing secret
echo "JWT_SECRET=$(openssl rand -base64 64)" >> .env.secrets
# AES-256-GCM key for encrypting secrets at rest (must be base64-encoded 32 bytes)
echo "ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env.secrets
# HMAC secret used by the FerrFlow CLI telemetry endpoint
echo "FERRFLOW_HMAC_SECRET=$(openssl rand -hex 32)" >> .env.secrets
Merge .env.secrets into .env manually, then start the stack:
docker compose -f docker-compose.selfhost.yml up -d
The dashboard is now available on http://localhost:3000.
Environment variables
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
yes | Postgres connection string |
JWT_SECRET |
yes | Signing key for session JWTs |
ENCRYPTION_KEY |
yes | 32-byte base64 key for the secrets at-rest encryption |
FERRFLOW_HMAC_SECRET |
yes | HMAC key for CLI telemetry ingestion |
SERVER_HOST |
no (default 0.0.0.0) |
Bind address |
SERVER_PORT |
no (default 3000) |
Bind port |
DATABASE_MAX_CONNECTIONS |
no (default 10) |
Postgres pool size |
RATE_LIMIT_REQUESTS |
no (default 60) |
Requests per minute before rate-limiting |
RUST_LOG |
no (default info) |
Log level |
Using an external Postgres
Remove the db service and the depends_on block from the compose file, then
point DATABASE_URL at your existing instance. The TimescaleDB extension is
required — create it once with:
CREATE EXTENSION IF NOT EXISTS timescaledb;
Migrations run automatically on startup.
Reverse proxy and TLS
Terminate TLS at a proxy in front of the container. Example Caddyfile:
ferrflow.example.com {
reverse_proxy localhost:3000
}
No CORS origins need to be whitelisted because the browser only talks to the single proxy origin.
Updating
docker compose -f docker-compose.selfhost.yml pull
docker compose -f docker-compose.selfhost.yml up -d
Database migrations are applied on startup. Take a database backup before upgrading across a major version.
Health check
GET /health returns 200 once the API has connected to Postgres and applied
migrations.