Skip to main content
Use this guide for a manual Docker Compose setup. It downloads the production Compose file and starts the baseline Formbricks stack: Formbricks Web, PostgreSQL, Redis/Valkey, Formbricks Hub, and Cube. Optional services such as Qwen/vLLM, AI taxonomy, and RustFS are documented after the baseline stack is running.

Requirements

Make sure the following tools are installed:
  • Docker Engine with Docker Compose V2 (docker compose)
  • curl
  • openssl
  • A POSIX-compatible shell such as sh, bash, or zsh
Docker and Docker Compose are usually included in tools like Docker Desktop and Rancher Desktop.
docker compose without the hyphen is now the primary method of using docker-compose, according to the Docker documentation.

Choose Your Setup Path

  • Use this manual Docker Compose guide for local installs, custom servers, or custom reverse-proxy setups.
  • Use the one-click setup script for production Ubuntu servers where you want Traefik, HTTPS certificates, and optional RustFS automation.
  • Use the migration guide before updating an existing Formbricks 4.x install or an older v5 compose file.
Starting with Formbricks v5, the production Docker Compose stack includes Formbricks Hub and Cube as part of the baseline. Keep HUB_API_URL at its internal default unless Hub runs elsewhere.

Start

  1. Create a New Directory for Formbricks Open a terminal and run the following commands to create and enter a new directory for Formbricks:
    mkdir formbricks-quickstart && cd formbricks-quickstart
    
  2. Download the Docker Files Get the Docker Compose file plus the Cube configuration shipped with the baseline stack:
    mkdir -p cube/schema
    curl -fsSL \
      -o docker-compose.yml \
      https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/docker-compose.yml
    curl -fsSL \
      -o cube/cube.js \
      https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/cube/cube.js
    curl -fsSL \
      -o cube/schema/FeedbackRecords.js \
      https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/cube/schema/FeedbackRecords.js
    
  3. Create the Environment File Store your configuration in .env. Docker Compose reads this file for variable interpolation, and the Formbricks container also loads it at startup. For a local install, use http://localhost:3000. For a server install, replace both URL values with your public HTTPS URL before starting the stack.
    cat <<EOF > .env
    WEBAPP_URL=http://localhost:3000
    NEXTAUTH_URL=http://localhost:3000
    NEXTAUTH_SECRET=$(openssl rand -hex 32)
    ENCRYPTION_KEY=$(openssl rand -hex 32)
    CRON_SECRET=$(openssl rand -hex 32)
    HUB_API_KEY=$(openssl rand -hex 32)
    CUBEJS_API_SECRET=$(openssl rand -hex 32)
    CUBEJS_JWT_ISSUER=formbricks-web
    CUBEJS_JWT_AUDIENCE=formbricks-cube
    EOF
    chmod 600 .env
    
  4. Validate the Docker Compose Configuration Validate the Compose file after .env contains the required values:
    docker compose config >/dev/null
    
    If validation fails, check that .env contains the required values and that docker-compose.yml has valid syntax.
  5. Start the Docker Setup Now, you’re ready to run Formbricks with Docker. Use the command below to start Formbricks together with PostgreSQL, Redis, Formbricks Hub, and Cube.
    docker compose up -d
    
    The -d flag runs the containers in the background, so they keep running even after you close the terminal.
  6. Verify the Stack Confirm the baseline services started and the Formbricks health endpoint responds:
    docker compose ps -a
    curl -fsS http://localhost:3000/health
    docker compose logs --tail=100 formbricks-migrate hub-migrate formbricks hub cube
    
    formbricks-migrate and hub-migrate should complete successfully. postgres, redis, cube, hub, and formbricks should be running or healthy.
  7. Open Formbricks in Your Browser Once the setup is running, open http://localhost:3000 in your browser to access Formbricks. The first time you visit, you’ll see a setup wizard. Follow the steps to create your first user and start using Formbricks.
The bundled Docker stack keeps Formbricks Hub and Cube internal to the compose network. The app reaches them through http://hub:8080 and http://cube:4000. When AI taxonomy beta is enabled, Hub reaches taxonomy internally through http://taxonomy:8000.

Optional Services

Start and verify the baseline stack before enabling optional services.
If .env already contains a COMPOSE_PROFILES line, update that line instead of adding a second one.

Enable Bundled Qwen/vLLM For AI

The Docker stack can optionally run Qwen through vLLM as an OpenAI-compatible /v1 endpoint. Baseline installs do not start vLLM and can still use Google Vertex, Azure, AWS Bedrock, or another OpenAI-compatible endpoint. The bundled Qwen/vLLM service requires a GPU-capable Docker host with the NVIDIA Container Toolkit installed. Model files are stored in the qwen-model-cache Docker volume. Startup can take several minutes while vLLM downloads and loads the model. To use the bundled Qwen runtime, add these values to .env:
cat <<EOF >> .env
COMPOSE_PROFILES=qwen
AI_PROVIDER=openai-compatible
AI_MODEL=qwen3-14b-awq
AI_OPENAI_COMPATIBLE_BASE_URL=http://vllm:8000/v1
AI_OPENAI_COMPATIBLE_PROVIDER_NAME=vllm
AI_OPENAI_COMPATIBLE_SUPPORTS_STRUCTURED_OUTPUTS=1
EOF
Then start the stack with the profile and check vLLM:
docker compose --profile qwen up -d
docker compose --profile qwen ps
docker compose logs --tail=100 vllm formbricks
curl -fsS http://127.0.0.1:8000/health
The vLLM endpoint is available inside the Compose network at http://vllm:8000/v1 and is bound to 127.0.0.1:8000 by default for local checks. If you run your own Qwen/vLLM service, do not enable the qwen profile. Set AI_PROVIDER, AI_MODEL, and AI_OPENAI_COMPATIBLE_BASE_URL to your external endpoint instead.

Enable AI Taxonomy Beta

The standalone AI taxonomy service is included as an opt-in Docker Compose profile. Baseline installs do not start taxonomy and do not require taxonomy or LLM secrets. To enable taxonomy with the bundled Qwen runtime, add these values to .env:
cat <<EOF >> .env
COMPOSE_PROFILES=qwen,taxonomy
TAXONOMY_SERVICE_URL=http://taxonomy:8000
TAXONOMY_SERVICE_TOKEN=$(openssl rand -hex 32)
HUB_INTERNAL_API_TOKEN=$(openssl rand -hex 32)
TAXONOMY_IMAGE_REF=:v0.1.0
TAXONOMY_LLM_PROVIDER=openai-compatible
TAXONOMY_LLM_MODEL=qwen3-14b-awq
TAXONOMY_LLM_BASE_URL=http://vllm:8000/v1
TAXONOMY_LLM_API_KEY=not-used
EOF
Replace :v0.1.0 with the current released ghcr.io/formbricks/taxonomy image tag for your Formbricks version. Production installs should pin a release tag instead of relying on :latest. If you run your own OpenAI-compatible endpoint, use COMPOSE_PROFILES=taxonomy instead and point TAXONOMY_LLM_BASE_URL at that /v1 endpoint. The selected model must reliably return strict JSON because taxonomy generation validates an exact 5-level tree. To use Amazon Bedrock instead of an OpenAI-compatible endpoint, replace the taxonomy LLM values with:
TAXONOMY_LLM_PROVIDER=bedrock
TAXONOMY_LLM_MODEL=eu.anthropic.claude-sonnet-4-5-20250929-v1:0
AWS_REGION=eu-north-1
AWS_BEARER_TOKEN_BEDROCK=<bedrock-api-key>
To use Gemini on Vertex AI instead, replace the taxonomy LLM values with:
TAXONOMY_LLM_PROVIDER=vertex-gemini
TAXONOMY_LLM_MODEL=gemini-2.5-flash
TAXONOMY_VERTEX_PROJECT=<google-cloud-project>
TAXONOMY_VERTEX_LOCATION=<vertex-location>
TAXONOMY_GOOGLE_CLOUD_CREDENTIALS_JSON=<service-account-json>
Start the stack after updating .env. Compose reads COMPOSE_PROFILES from .env, so the same command works for taxonomy and qwen,taxonomy setups:
docker compose up -d
docker compose ps
docker compose logs --tail=100 taxonomy hub
If you enabled the bundled Qwen profile, also check vLLM:
docker compose logs --tail=100 vllm
Run the authenticated preflight after startup to verify Hub internal auth and LLM reachability:
docker compose --profile taxonomy exec -T taxonomy python - <<'PY'
import os
import urllib.request

request = urllib.request.Request(
    "http://127.0.0.1:8000/v1/preflight",
    headers={"Authorization": "Bearer " + os.environ["TAXONOMY_SERVICE_TOKEN"]},
)
print(urllib.request.urlopen(request, timeout=10).read().decode())
PY
This command runs inside the taxonomy container, so 127.0.0.1:8000 refers to the taxonomy service itself. The preflight endpoint then checks Hub internal auth and LLM reachability from taxonomy. The taxonomy service remains internal to the compose network by default. For production workloads, TAXONOMY_MAX_RECORDS defaults to 50000; override it only as an advanced safety limit after sizing CPU, memory, and LLM capacity.
The taxonomy service is internal to the Docker network. Formbricks Web still calls Hub with HUB_API_KEY; Hub calls taxonomy with TAXONOMY_SERVICE_TOKEN; taxonomy calls Hub internal APIs with HUB_INTERNAL_API_TOKEN.
The one-click installer does not prompt for AI taxonomy settings. One-click users can enable the beta later by editing ./formbricks/.env, adding the taxonomy variables above, ensuring COMPOSE_PROFILES=taxonomy is set, and restarting with docker compose up -d.
If you use the one-click Traefik setup, FeedbackRecords are available on the Formbricks origin at /api/v3/feedbackRecords and /v1/feedback-records. Custom Docker reverse proxies need equivalent wiring: run gateway auth against the Formbricks app, rewrite /api/v3/feedbackRecords to Hub’s /v1/feedback-records, and inject Authorization: Bearer <HUB_API_KEY> only on the Hub-bound hop.

Update

See our migration guide for version-specific steps to update Formbricks.
For a major migration such as Formbricks 4.x to 5.0, update your compose structure and configuration first. Pulling images alone is not enough if your stack does not yet include Hub (HUB_API_KEY), Cube (cube/ config files plus CUBEJS_API_SECRET), or the new edge rate-limiting setup.
  1. Pull the latest Formbricks image
    docker compose pull
    
  2. Stop the Formbricks stack
    docker compose down
    
  3. Re-start the Formbricks stack with the updated image
    docker compose up -d
    

Optional: Add RustFS for File Storage

RustFS provides S3-compatible object storage for file uploads in Formbricks. It is not required for the baseline Docker setup. Add it only when you want features like image uploads, survey file uploads, or custom logos.
For a broader overview of file storage options and required environment variables, see our File Uploads Configuration guide.
For production deployments with HTTPS, use the one-click setup script which automatically configures RustFS with Traefik, SSL certificates, a dedicated files. subdomain, and least-privilege service credentials. The examples below are best suited for development, testing, or custom local setups.
The bundled RustFS examples on this page are convenience-oriented single-server setups. They work well for development, evaluation, and smaller self-hosted deployments, but they are not the ideal RustFS architecture for high-availability or larger-scale production storage. For stricter production requirements, use external object storage or run a dedicated RustFS deployment separately.

Quick Start: Repository Development Stack

If you cloned the Formbricks repository, the fastest way to test file uploads locally is to use the included docker-compose.dev.yml, which already starts RustFS and auto-creates the formbricks bucket.
This development compose file is not downloaded by the manual production quickstart above. If you only downloaded docker-compose.yml, use the manual RustFS setup below or the one-click production setup.
  1. Start the local stack From the repository root:
    docker compose -f docker-compose.dev.yml up -d
    
    This starts PostgreSQL, Valkey (Redis), Mailhog, RustFS, a permissions helper, a one-time bucket bootstrap job, Formbricks Hub, and a local Cube instance for analytics testing.
  2. Access the RustFS console Open http://localhost:9001 in your browser and sign in with:
    • Username: devrustfs
    • Password: devrustfs123
  3. Configure Formbricks Update your .env file or environment variables:
    S3_ACCESS_KEY="devrustfs"
    S3_SECRET_KEY="devrustfs123"
    S3_REGION="us-east-1"
    S3_BUCKET_NAME="formbricks"
    S3_ENDPOINT_URL="http://localhost:9000"
    S3_FORCE_PATH_STYLE="1"
    
  4. Verify uploads After uploading a file in Formbricks, open http://localhost:9001 and navigate to Buckets → formbricks to confirm the object was stored successfully.
The development compose file also runs a rustfs-init job so you do not need to create the bucket manually.

Manual RustFS Setup (Custom Configuration)

Recommended: Prefer docker-compose.dev.yml for local development unless you need to fold RustFS into an existing custom Compose stack.
If you want to add RustFS to your own docker-compose.yml, use a pinned RustFS image plus two helper services:
services:
  rustfs-perms:
    image: busybox:1.36.1
    user: "0:0"
    command: ["sh", "-c", "mkdir -p /data && chown -R 10001:10001 /data"]
    volumes:
      - rustfs-data:/data

  rustfs:
    image: rustfs/rustfs:1.0.0-alpha.93
    restart: always
    depends_on:
      rustfs-perms:
        condition: service_completed_successfully
    command: /data
    environment:
      RUSTFS_ACCESS_KEY: "${FORMBRICKS_RUSTFS_ADMIN_USER}"
      RUSTFS_SECRET_KEY: "${FORMBRICKS_RUSTFS_ADMIN_PASSWORD}"
      RUSTFS_ADDRESS: ":9000"
      RUSTFS_CONSOLE_ENABLE: "true"
      RUSTFS_CONSOLE_ADDRESS: ":9001"
      # CORS for browser uploads: set to your Formbricks origin(s), comma-separated.
      # Unset = allow all origins; a value is treated as an allow-list. See the File Uploads guide.
      RUSTFS_CORS_ALLOWED_ORIGINS: "https://app.yourdomain.com"
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - rustfs-data:/data

  rustfs-init:
    image: minio/mc@sha256:95b5f3f7969a5c5a9f3a700ba72d5c84172819e13385aaf916e237cf111ab868
    depends_on:
      - rustfs
    environment:
      RUSTFS_ADMIN_USER: "${FORMBRICKS_RUSTFS_ADMIN_USER}"
      RUSTFS_ADMIN_PASSWORD: "${FORMBRICKS_RUSTFS_ADMIN_PASSWORD}"
      RUSTFS_SERVICE_USER: "${FORMBRICKS_RUSTFS_SERVICE_USER}"
      RUSTFS_SERVICE_PASSWORD: "${FORMBRICKS_RUSTFS_SERVICE_PASSWORD}"
      RUSTFS_BUCKET_NAME: "${FORMBRICKS_RUSTFS_BUCKET_NAME}"
      RUSTFS_POLICY_NAME: "${FORMBRICKS_RUSTFS_POLICY_NAME}"
    entrypoint:
      - /bin/sh
      - -c
      - |
        set -e
        until mc alias set rustfs http://rustfs:9000 "$RUSTFS_ADMIN_USER" "$RUSTFS_ADMIN_PASSWORD" >/dev/null 2>&1 \
          && mc ls rustfs >/dev/null 2>&1; do
          sleep 2
        done
        mc mb rustfs/"$RUSTFS_BUCKET_NAME" --ignore-existing
        cat > /tmp/formbricks-policy.json << EOF
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": ["s3:DeleteObject", "s3:GetObject", "s3:PutObject"],
              "Resource": ["arn:aws:s3:::$RUSTFS_BUCKET_NAME/*"]
            },
            {
              "Effect": "Allow",
              "Action": ["s3:ListBucket"],
              "Resource": ["arn:aws:s3:::$RUSTFS_BUCKET_NAME"]
            }
          ]
        }
        EOF
        if ! mc admin policy info rustfs "$RUSTFS_POLICY_NAME" >/dev/null 2>&1; then
          mc admin policy create rustfs "$RUSTFS_POLICY_NAME" /tmp/formbricks-policy.json || \
            mc admin policy add rustfs "$RUSTFS_POLICY_NAME" /tmp/formbricks-policy.json
        fi
        if ! mc admin user info rustfs "$RUSTFS_SERVICE_USER" >/dev/null 2>&1; then
          mc admin user add rustfs "$RUSTFS_SERVICE_USER" "$RUSTFS_SERVICE_PASSWORD"
        fi
        mc admin policy attach rustfs "$RUSTFS_POLICY_NAME" --user "$RUSTFS_SERVICE_USER"
Declare the corresponding volume:
volumes:
  rustfs-data:
    driver: local
Store the generated RustFS credentials in a local .env file next to your docker-compose.yml instead of hardcoding them in Compose:
FORMBRICKS_RUSTFS_ADMIN_USER=formbricks-root
FORMBRICKS_RUSTFS_ADMIN_PASSWORD=change-this-secure-password
FORMBRICKS_RUSTFS_SERVICE_USER=formbricks-service
FORMBRICKS_RUSTFS_SERVICE_PASSWORD=change-this-service-password
FORMBRICKS_RUSTFS_BUCKET_NAME=formbricks
FORMBRICKS_RUSTFS_POLICY_NAME=formbricks-policy
FORMBRICKS_RUSTFS_REGION=us-east-1
Then configure Formbricks to use the RustFS service credentials:
S3_ACCESS_KEY="${FORMBRICKS_RUSTFS_SERVICE_USER}"
S3_SECRET_KEY="${FORMBRICKS_RUSTFS_SERVICE_PASSWORD}"
S3_REGION="${FORMBRICKS_RUSTFS_REGION}"
S3_BUCKET_NAME="${FORMBRICKS_RUSTFS_BUCKET_NAME}"
S3_ENDPOINT_URL="http://rustfs:9000"
S3_FORCE_PATH_STYLE="1"
Start the stack:
docker compose up -d
The bucket and service account are created automatically by the rustfs-init job defined above, so no manual RustFS console step is required.
Restrict the .env file to 0600 and do not commit it to source control. For production, prefer the one-click setup script, which creates a separate least-privilege service account automatically.

Tips & Common Gotchas

  • Permission denied on /data: Ensure the mounted directory or volume is owned by UID 10001. The rustfs-perms helper handles this for Compose-managed volumes.
  • Storage medium matters: Prefer local SSD or NVMe storage for rustfs-data, use XFS on dedicated host-managed disks where possible, and avoid NFS or other network filesystems for RustFS data.
  • Connection refused: Ensure the rustfs container is running and port 9000 is reachable from the Formbricks container.
  • Bucket not found: Confirm that rustfs-init completed successfully or create the bucket manually with mc.
  • Auth failed: Confirm that S3_ACCESS_KEY and S3_SECRET_KEY match the RustFS credentials configured on the server.
  • Backups: Back up the rustfs-data volume regularly, especially for single-server deployments.
  • Console exposure: Do not expose the RustFS console port publicly in production. Keep it on a private network or behind admin-only controls.
  • Health check: From the Formbricks container:
    docker compose exec formbricks sh -c 'wget -O- http://rustfs:9000/health'
    

Production Setup with Traefik

For production deployments, use the one-click setup script, which automatically configures:
  • RustFS behind Traefik on a dedicated files.yourdomain.com subdomain
  • Automatic SSL certificate generation via Let’s Encrypt
  • CORS configuration scoped to your Formbricks domain
  • Rate limiting middleware
  • Separate RustFS admin and Formbricks service credentials
  • A rustfs-init job that creates the bucket and access policy
The production setup from formbricks.sh adds the reverse proxy wiring and bootstrap automation needed for long-lived deployments.
Even in the one-click flow, bundled RustFS remains a convenience-oriented single-server deployment. For higher availability, stricter operational requirements, or larger storage footprints, prefer external object storage or a dedicated RustFS deployment managed separately from Formbricks.

Debug

If startup fails, first check the resolved configuration and container state:
docker compose config >/dev/null
docker compose ps -a
Then inspect the services that commonly explain startup issues:
docker compose logs --tail=200 formbricks-migrate hub-migrate formbricks hub cube
docker compose logs --tail=200 postgres redis
Common checks:
  • Missing or empty secrets: Confirm .env contains NEXTAUTH_SECRET, ENCRYPTION_KEY, CRON_SECRET, HUB_API_KEY, and CUBEJS_API_SECRET.
  • Migration failures: Check formbricks-migrate, hub-migrate, and postgres logs. Do not remove Docker volumes on an existing install unless you intend to delete its data.
  • Cube is unhealthy: Confirm cube/cube.js, cube/schema/FeedbackRecords.js, and CUBEJS_API_SECRET exist, then inspect docker compose logs cube.
  • Hub auth errors: Confirm the same HUB_API_KEY from .env is used by Formbricks Web and the Hub service.
  • Port conflicts: Confirm ports 3000, 6379, and any optional service ports you enabled are not already in use on the host.
  • Optional Qwen/vLLM issues: Check GPU availability, NVIDIA Container Toolkit installation, and docker compose --profile qwen logs --tail=200 vllm.
To edit any of the available environment variables, check out our Configuration section.
If you have any questions or require help, reach out on GitHub Discussions.