--- title: Quick Start Guide | Formbricks Hub description: Get Formbricks Hub running locally in minutes. --- ## Prerequisites - **Docker** and **Docker Compose**: [Install Docker Desktop](https://www.docker.com/products/docker-desktop/) ## Installation ### Option 1: Docker Compose (Recommended) The easiest way to run Formbricks Hub with all dependencies. #### 1. Create the Docker Compose File Create a new directory: Terminal window ``` mkdir formbricks-hub && cd formbricks-hub ``` Create `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: bridge ``` #### 2. Configure Environment Variables Create a `.env` file in the same directory: Terminal window ``` # Required: Secure your PostgreSQL database POSTGRES_PASSWORD=your_secure_postgres_password_here # Required: API authentication key API_KEY=your_secure_api_key_here # Optional: Host ports and Hub image tag HUB_PORT=8080 POSTGRES_PORT=5432 HUB_IMAGE_TAG=latest LOG_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= ``` Generate secure passwords and API keys: Terminal window ``` # For POSTGRES_PASSWORD openssl rand -base64 32 # For API_KEY openssl rand -base64 32 ``` #### 3. Start Hub Terminal window ``` docker compose up -d ``` This will: - Start PostgreSQL with persistent storage - Run database migrations and create River tables with the one-shot `hub-migrate` service - 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](https://github.com/pressly/goose) and [River](https://riverqueue.com/). 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 Check that the services are running: Terminal window ``` docker compose ps --all ``` Expected output: ``` NAME STATUS PORTS formbricks_hub_api Up (healthy) 0.0.0.0:8080->8080/tcp formbricks_hub_migrate Exited (0) formbricks_hub_postgres Up (healthy) 0.0.0.0:5432->5432/tcp formbricks_hub_worker Up ``` Test the health endpoint: Terminal window ``` curl http://localhost:8080/health ``` Expected response: ``` OK ``` Check that the runtime OpenAPI spec is available without authentication: Terminal window ``` curl http://localhost:8080/openapi.json ``` Expected response: an OpenAPI JSON document with Hub API metadata and paths. ### Option 2: Docker Run (Minimal) If you have an existing PostgreSQL instance, run migrations first: Terminal window ``` 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: Terminal window ``` 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:latest ``` ## Making Your First API Call ### 1. Create a Feedback Record Terminal window ``` 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" } }' ``` All API requests require the `Authorization: Bearer <token>` header with your configured `API_KEY`. 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 Terminal window ``` 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 Terminal window ``` 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 To get the total number of records matching a filter—without paging through results—use the count endpoint: Terminal window ``` 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 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: Terminal window ``` 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_id`s with the taxonomy tree (`GET /v1/taxonomy/runs/{run_id}/tree`) to show counts alongside each node. See the [API Reference](/api/index.md) for the full taxonomy surface. ## Configuration ### 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 ->](/reference/environment-variables/index.md) ### Database Connection String Format ``` postgresql://username:password@host:port/database?sslmode=disable ``` For Docker Compose (services in same network): ``` postgresql://formbricks:password@postgres:5432/hub?sslmode=disable ``` For external PostgreSQL: ``` postgresql://user:pass@db.example.com:5432/hub?sslmode=require ``` ## Managing the Service ### View Logs Terminal window ``` # All services docker compose logs -f # Just the Hub API docker compose logs -f hub # Just the Hub worker docker compose logs -f hub-worker # Just PostgreSQL docker compose logs -f postgres ``` ### Stop Services Terminal window ``` docker compose down ``` ### Stop and Remove Data **Warning:** This will delete all stored feedback records. Terminal window ``` docker compose down -v ``` ### Restart Services Terminal window ``` docker compose restart ``` ### Update to Latest Version Terminal window ``` docker compose pull docker compose stop hub hub-worker docker compose up --force-recreate hub-migrate docker compose up -d ``` When 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 - [Data Model](/core-concepts/data-model/index.md) - Understand the feedback record structure - [Authentication](/core-concepts/authentication/index.md) - Learn about API security - [API Reference](/api/index.md) - Explore all endpoints - [Environment Variables](/reference/environment-variables/index.md) - Configure your deployment ## Troubleshooting ### Port Already in Use If port 8080 or 5432 is occupied: Terminal window ``` # Change ports in .env HUB_PORT=8081 POSTGRES_PORT=5433 ``` Or modify the port mapping in `compose.yml`: ``` ports: - "${HUB_PORT:-8080}:8080" # Host:Container ``` ### Container Fails to Start Check the logs for errors: Terminal window ``` docker compose logs hub ``` Common issues: - **Database not ready**: Wait for PostgreSQL healthcheck to pass - **Invalid API key**: Ensure `API_KEY` is set in `.env` - **Database connection failed**: Check `DATABASE_URL` format ### Database Connection Failed Verify PostgreSQL is running: Terminal window ``` docker compose ps postgres ``` If unhealthy, check PostgreSQL logs: Terminal window ``` docker compose logs postgres ``` ### Health Check Failing Check if the service is listening: Terminal window ``` docker exec formbricks_hub_api wget -O- http://localhost:8080/health ``` ### Cannot Pull Docker Image Ensure you have access to GitHub Container Registry: Terminal window ``` docker pull ghcr.io/formbricks/hub:latest ``` If authentication is required: Terminal window ``` echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin ``` ### Data Not Persisting Verify the volume is mounted: Terminal window ``` docker volume ls | grep postgres_data docker volume inspect formbricks-hub_postgres_data ``` ## Getting Help - **GitHub Discussions**: [Ask questions and share ideas](https://github.com/formbricks/hub/discussions) - **GitHub Issues**: [Report bugs or request features](https://github.com/formbricks/hub/issues) - **API Reference**: [Interactive documentation](/api/index.md)