Quick Start Guide
Get Formbricks Hub running locally in minutes.
Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose: Install Docker Desktop
Installation
Section titled “Installation”Option 1: Docker Compose (Recommended)
Section titled “Option 1: Docker Compose (Recommended)”The easiest way to run Formbricks Hub with all dependencies.
1. Create the Docker Compose File
Section titled “1. Create the Docker Compose File”Create a new directory:
mkdir formbricks-hub && cd formbricks-hubCreate compose.yml:
services: postgres: image: pgvector/pgvector:pg18 container_name: formbricks_hub_postgres restart: unless-stopped environment: POSTGRES_USER: formbricks POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-formbricks_dev} POSTGRES_DB: hub ports: - "${POSTGRES_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U formbricks -d hub"] interval: 10s timeout: 5s retries: 5 networks: - formbricks_hub command: > postgres -c shared_preload_libraries=vector
hub-migrate: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-latest} container_name: formbricks_hub_migrate restart: "no" depends_on: postgres: condition: service_healthy environment: DATABASE_URL: postgresql://formbricks:${POSTGRES_PASSWORD:-formbricks_dev}@postgres:5432/hub?sslmode=disable entrypoint: ["/bin/sh", "-c"] command: > /usr/local/bin/goose -dir /app/migrations postgres "$$DATABASE_URL" up && /usr/local/bin/river migrate-up --database-url "$$DATABASE_URL" networks: - formbricks_hub
hub: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-latest} container_name: formbricks_hub_api restart: unless-stopped depends_on: postgres: condition: service_healthy hub-migrate: condition: service_completed_successfully environment: DATABASE_URL: postgresql://formbricks:${POSTGRES_PASSWORD:-formbricks_dev}@postgres:5432/hub?sslmode=disable API_KEY: ${API_KEY:-} PORT: 8080 PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-} LOG_LEVEL: ${LOG_LEVEL:-info} ports: - "${HUB_PORT:-8080}:8080" healthcheck: test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health", ] interval: 30s timeout: 5s retries: 3 start_period: 10s networks: - formbricks_hub
hub-worker: image: ghcr.io/formbricks/hub:${HUB_IMAGE_TAG:-latest} container_name: formbricks_hub_worker restart: unless-stopped depends_on: postgres: condition: service_healthy hub-migrate: condition: service_completed_successfully environment: DATABASE_URL: postgresql://formbricks:${POSTGRES_PASSWORD:-formbricks_dev}@postgres:5432/hub?sslmode=disable LOG_LEVEL: ${LOG_LEVEL:-info} entrypoint: ["/app/hub-worker"] healthcheck: disable: true networks: - formbricks_hub
volumes: postgres_data: driver: local
networks: formbricks_hub: driver: bridge2. Configure Environment Variables
Section titled “2. Configure Environment Variables”Create a .env file in the same directory:
# Required: Secure your PostgreSQL databasePOSTGRES_PASSWORD=your_secure_postgres_password_here
# Required: API authentication keyAPI_KEY=your_secure_api_key_here
# Optional: Host ports and Hub image tagHUB_PORT=8080POSTGRES_PORT=5432HUB_IMAGE_TAG=latestLOG_LEVEL=info
# Optional: Public URL advertised in /openapi.yaml and /openapi.json# Set this for production behind ingress, TLS termination, or a path prefix.PUBLIC_BASE_URL=# For POSTGRES_PASSWORDopenssl rand -base64 32
# For API_KEYopenssl rand -base64 323. Start Hub
Section titled “3. Start Hub”docker compose up -dThis will:
- Start PostgreSQL with persistent storage
- Run database migrations and create River tables with the one-shot
hub-migrateservice - Pull the latest Formbricks Hub image from GitHub Container Registry
- Start the Hub API on port 8080
- Start the Hub worker for asynchronous jobs
Migrations are managed with goose and River. The published Hub image includes goose, river, and /app/migrations, so Docker Compose can migrate the database without local Go, goose, river, or migration files.
4. Verify Installation
Section titled “4. Verify Installation”Check that the services are running:
docker compose ps --allExpected output:
NAME STATUS PORTSformbricks_hub_api Up (healthy) 0.0.0.0:8080->8080/tcpformbricks_hub_migrate Exited (0)formbricks_hub_postgres Up (healthy) 0.0.0.0:5432->5432/tcpformbricks_hub_worker UpTest the health endpoint:
curl http://localhost:8080/healthExpected response:
OKCheck that the runtime OpenAPI spec is available without authentication:
curl http://localhost:8080/openapi.jsonExpected response: an OpenAPI JSON document with Hub API metadata and paths.
Option 2: Docker Run (Minimal)
Section titled “Option 2: Docker Run (Minimal)”If you have an existing PostgreSQL instance, run migrations first:
docker run --rm \ -e DATABASE_URL="postgresql://user:password@host:5432/hub?sslmode=disable" \ --entrypoint /bin/sh \ ghcr.io/formbricks/hub:latest \ -c 'goose -dir /app/migrations postgres "$DATABASE_URL" up && river migrate-up --database-url "$DATABASE_URL"'The published Hub image includes goose, river, and /app/migrations, so
this applies both Hub database migrations and River queue migrations without
local Go, goose, river, or migration files.
Then run the Hub API:
docker run -d \ --name formbricks-hub \ -p 8080:8080 \ -e DATABASE_URL="postgresql://user:password@host:5432/hub?sslmode=disable" \ -e API_KEY="your-secret-key" \ -e LOG_LEVEL="info" \ ghcr.io/formbricks/hub:latestMaking Your First API Call
Section titled “Making Your First API Call”1. Create a Feedback Record
Section titled “1. Create a Feedback Record”curl -X POST http://localhost:8080/v1/feedback-records \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_secure_api_key_here" \ -d '{ "tenant_id": "org-123", "submission_id": "subm-my-first-survey-001", "source_type": "survey", "source_id": "my-first-survey", "field_id": "q1", "field_label": "How satisfied are you?", "field_type": "rating", "value_number": 5, "metadata": { "country": "US", "device": "desktop" } }'Expected response:
{ "id": "01932c8a-8b9e-7000-8000-000000000001", "tenant_id": "org-123", "submission_id": "subm-my-first-survey-001", "collected_at": "2025-10-20T12:34:56Z", "created_at": "2025-10-20T12:34:56Z", "updated_at": "2025-10-20T12:34:56Z", "source_type": "survey", "source_id": "my-first-survey", "field_id": "q1", "field_type": "rating", "value_number": 5, "metadata": { "country": "US", "device": "desktop" }}2. Create a Text Feedback Record
Section titled “2. Create a Text Feedback Record”curl -X POST http://localhost:8080/v1/feedback-records \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_secure_api_key_here" \ -d '{ "tenant_id": "org-123", "submission_id": "subm-my-first-survey-001", "source_type": "survey", "source_id": "my-first-survey", "field_id": "q2", "field_label": "What can we improve?", "field_type": "text", "value_text": "The checkout process could be simplified!" }'3. Query Feedback Records
Section titled “3. Query Feedback Records”curl "http://localhost:8080/v1/feedback-records?tenant_id=org-123&limit=10" \ -H "Authorization: Bearer your_secure_api_key_here"Response:
{ "data": [ { "id": "01932c8a-8b9e-7000-8000-000000000001", "tenant_id": "org-123", "submission_id": "subm-my-first-survey-001", "collected_at": "2025-10-20T12:34:56Z", "created_at": "2025-10-20T12:34:56Z", "updated_at": "2025-10-20T12:34:56Z", "source_type": "survey", "source_id": "my-first-survey", "field_id": "q1", "field_type": "rating", "value_number": 5 } ], "limit": 10}4. Count Feedback Records
Section titled “4. Count Feedback Records”To get the total number of records matching a filter—without paging through results—use the count endpoint:
curl "http://localhost:8080/v1/feedback-records/count?tenant_id=org-123" \ -H "Authorization: Bearer your_secure_api_key_here"Response:
{ "count": 42}It accepts the same filters as the query endpoint (source_type, field_id, user_id, since/until, and so on), so you can count any subset—for example, only survey responses in the last week.
5. Count Records per Taxonomy Node
Section titled “5. Count Records per Taxonomy Node”If you’ve generated a taxonomy for a dataset, you can fetch how many feedback records fall under each topic and subtopic in a single call:
curl "http://localhost:8080/v1/taxonomy/runs/RUN_ID/record-counts?tenant_id=org-123" \ -H "Authorization: Bearer your_secure_api_key_here"Response:
{ "counts": [ { "node_id": "019f6b2a-1e40-7c93-8a11-3d5e7f901a2b", "record_count": 128 }, { "node_id": "019f6b2a-1e40-7c93-8a11-3d5e7f901b7c", "record_count": 47 }, { "node_id": "019f6b2a-1e40-7c93-8a11-3d5e7f901c3d", "record_count": 19 } ]}Each record_count is a subtree total: a topic reports the number of distinct records across all of its subtopics, and the root reports the run total. Join the node_ids with the taxonomy tree (GET /v1/taxonomy/runs/{run_id}/tree) to show counts alongside each node. See the API Reference for the full taxonomy surface.
Configuration
Section titled “Configuration”Environment Variables
Section titled “Environment Variables”| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | No | postgres://postgres:postgres@localhost:5432/test_db?sslmode=disable | PostgreSQL connection string |
API_KEY | Yes | - | API authentication key |
PORT | No | 8080 | HTTP server port |
PUBLIC_BASE_URL | No | - | Public API root advertised in runtime OpenAPI specs |
LOG_LEVEL | No | info | Log level (debug, info, warn, error) |
In the Docker Compose example above, use HUB_PORT and POSTGRES_PORT to change
host ports without changing the ports used inside the containers.
See full environment variable reference ->
Database Connection String Format
Section titled “Database Connection String Format”postgresql://username:password@host:port/database?sslmode=disableFor Docker Compose (services in same network):
postgresql://formbricks:password@postgres:5432/hub?sslmode=disableFor external PostgreSQL:
postgresql://user:pass@db.example.com:5432/hub?sslmode=requireManaging the Service
Section titled “Managing the Service”View Logs
Section titled “View Logs”# All servicesdocker compose logs -f
# Just the Hub APIdocker compose logs -f hub
# Just the Hub workerdocker compose logs -f hub-worker
# Just PostgreSQLdocker compose logs -f postgresStop Services
Section titled “Stop Services”docker compose downStop and Remove Data
Section titled “Stop and Remove Data”docker compose down -vRestart Services
Section titled “Restart Services”docker compose restartUpdate to Latest Version
Section titled “Update to Latest Version”docker compose pulldocker compose stop hub hub-workerdocker compose up --force-recreate hub-migratedocker compose up -dWhen updating Hub images, recreate hub-migrate so goose and River migrations
from the new image are applied before the API and worker continue running.
Next Steps
Section titled “Next Steps”- Data Model - Understand the feedback record structure
- Authentication - Learn about API security
- API Reference - Explore all endpoints
- Environment Variables - Configure your deployment
Troubleshooting
Section titled “Troubleshooting”Port Already in Use
Section titled “Port Already in Use”If port 8080 or 5432 is occupied:
# Change ports in .envHUB_PORT=8081POSTGRES_PORT=5433Or modify the port mapping in compose.yml:
ports: - "${HUB_PORT:-8080}:8080" # Host:ContainerContainer Fails to Start
Section titled “Container Fails to Start”Check the logs for errors:
docker compose logs hubCommon issues:
- Database not ready: Wait for PostgreSQL healthcheck to pass
- Invalid API key: Ensure
API_KEYis set in.env - Database connection failed: Check
DATABASE_URLformat
Database Connection Failed
Section titled “Database Connection Failed”Verify PostgreSQL is running:
docker compose ps postgresIf unhealthy, check PostgreSQL logs:
docker compose logs postgresHealth Check Failing
Section titled “Health Check Failing”Check if the service is listening:
docker exec formbricks_hub_api wget -O- http://localhost:8080/healthCannot Pull Docker Image
Section titled “Cannot Pull Docker Image”Ensure you have access to GitHub Container Registry:
docker pull ghcr.io/formbricks/hub:latestIf authentication is required:
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdinData Not Persisting
Section titled “Data Not Persisting”Verify the volume is mounted:
docker volume ls | grep postgres_datadocker volume inspect formbricks-hub_postgres_dataGetting Help
Section titled “Getting Help”- GitHub Discussions: Ask questions and share ideas
- GitHub Issues: Report bugs or request features
- API Reference: Interactive documentation