feat: Add Formbricks Hub to Docker Compose and Helm chart (#7284)

This commit is contained in:
Tiago
2026-02-23 13:35:33 +00:00
committed by GitHub
parent 817e108ff5
commit 337aedf463
10 changed files with 308 additions and 3 deletions
+8
View File
@@ -38,6 +38,14 @@ LOG_LEVEL=info
DATABASE_URL='postgresql://postgres:postgres@localhost:5432/formbricks?schema=public'
#################
# HUB (DEV) #
#################
# The dev stack (pnpm db:up / pnpm go) runs Formbricks Hub on port 8080.
# Set explicitly to avoid confusion; override as needed when using docker-compose.dev.yml.
HUB_API_KEY=dev-api-key
HUB_DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable
################
# MAIL SETUP #
################
+8
View File
@@ -7,6 +7,14 @@ It also truncates the name to a maximum of 63 characters and removes trailing hy
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Hub resource name: base name truncated to 59 chars then "-hub" so the suffix is never lost (63 char limit).
*/}}
{{- define "formbricks.hubname" -}}
{{- $base := include "formbricks.name" . | trunc 59 | trimSuffix "-" }}
{{- printf "%s-hub" $base | trimSuffix "-" }}
{{- end }}
{{/*
Define the application version to be used in labels.
@@ -0,0 +1,85 @@
{{- if .Values.hub.enabled }}
{{- if not .Values.hub.existingSecret }}
{{- fail "hub.existingSecret must be set when hub.enabled=true" }}
{{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "formbricks.hubname" . }}
labels:
helm.sh/chart: {{ include "formbricks.chart" . }}
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: hub
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: {{ .Values.partOfOverride | default (include "formbricks.name" .) }}
spec:
replicas: {{ .Values.hub.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: hub
spec:
{{- if .Values.deployment.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.deployment.imagePullSecrets | nindent 8 }}
{{- end }}
containers:
- name: hub
image: {{ .Values.hub.image.repository }}:{{ .Values.hub.image.tag | default "latest" }}
imagePullPolicy: {{ .Values.hub.image.pullPolicy }}
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
ports:
- name: http
containerPort: 8080
protocol: TCP
{{- if .Values.hub.existingSecret }}
envFrom:
- secretRef:
name: {{ .Values.hub.existingSecret }}
{{- end }}
{{- if .Values.hub.env }}
env:
{{- range $key, $value := .Values.hub.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- if .Values.hub.resources }}
resources:
{{- toYaml .Values.hub.resources | nindent 12 }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 5
timeoutSeconds: 5
successThreshold: 1
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 5
timeoutSeconds: 5
successThreshold: 1
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 10
{{- end }}
@@ -0,0 +1,53 @@
{{- if and .Values.hub.enabled .Values.hub.existingSecret }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "formbricks.hubname" . }}-migration
labels:
helm.sh/chart: {{ include "formbricks.chart" . }}
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: hub-migration
app.kubernetes.io/managed-by: {{ .Release.Service }}
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-5"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
ttlSecondsAfterFinished: {{ .Values.hub.migration.ttlSecondsAfterFinished | default 300 }}
backoffLimit: {{ .Values.hub.migration.backoffLimit | default 3 }}
activeDeadlineSeconds: {{ .Values.hub.migration.activeDeadlineSeconds | default 900 }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: hub-migration
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 1000
{{- if .Values.deployment.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.deployment.imagePullSecrets | nindent 8 }}
{{- end }}
containers:
- name: hub-migrate
image: {{ .Values.hub.image.repository }}:{{ .Values.hub.image.tag | default "latest" }}
imagePullPolicy: {{ .Values.hub.image.pullPolicy }}
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
command:
- sh
- -c
- |
/usr/local/bin/goose -dir /app/migrations postgres "$DATABASE_URL" up && \
/usr/local/bin/river migrate-up --database-url "$DATABASE_URL"
envFrom:
- secretRef:
name: {{ .Values.hub.existingSecret }}
{{- end }}
@@ -0,0 +1,24 @@
{{- if .Values.hub.enabled }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "formbricks.hubname" . }}
labels:
helm.sh/chart: {{ include "formbricks.chart" . }}
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: hub
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: {{ .Values.partOfOverride | default (include "formbricks.name" .) }}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ include "formbricks.hubname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP
{{- end }}
+34 -1
View File
@@ -304,6 +304,39 @@ serviceMonitor:
path: /metrics
port: metrics
##########################################################
# Hub API Configuration
# Formbricks Hub image: ghcr.io/formbricks/hub
##########################################################
hub:
enabled: false
replicas: 1
image:
repository: "ghcr.io/formbricks/hub"
# Pin to a semver tag for reproducible deployments; update on each Hub release.
tag: "1.0.0"
pullPolicy: IfNotPresent
# Secret containing API_KEY and DATABASE_URL for Hub. Use the same app secret (or same DATABASE_URL) to share the Formbricks database.
existingSecret: ""
# Optional env vars (non-secret). Use existingSecret for API_KEY and DATABASE_URL.
env: {}
# Migration job runs goose + river before Hub API starts (pre-install/pre-upgrade hook). Requires existingSecret with DATABASE_URL.
migration:
ttlSecondsAfterFinished: 300
backoffLimit: 3
activeDeadlineSeconds: 900
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
cpu: "100m"
##########################################################
# PostgreSQL Configuration
##########################################################
@@ -316,7 +349,7 @@ postgresql:
fullnameOverride: "formbricks-postgresql"
image:
repository: pgvector/pgvector
tag: 0.8.0-pg17
tag: pg18
auth:
username: formbricks
database: formbricks
+43 -1
View File
@@ -1,14 +1,25 @@
services:
# PostgreSQL must load the vector library so Hub (and Formbricks) can use the pgvector extension.
postgres:
image: pgvector/pgvector:pg17
image: pgvector/pgvector:pg18
volumes:
- postgres:/var/lib/postgresql/data
- ./docker/postgres-init-dev:/docker-entrypoint-initdb.d:ro
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432:5432
command: >
postgres
-c shared_preload_libraries=vector
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres || exit 1"]
interval: 5s
timeout: 3s
retries: 30
start_period: 10s
mailhog:
image: arjenz/mailhog
@@ -36,6 +47,37 @@ services:
volumes:
- minio-data:/data
# Run Hub DB migrations (goose + river) before the API starts. Idempotent; runs on every compose up.
hub-migrate:
image: ghcr.io/formbricks/hub:latest
restart: "no"
entrypoint: ["sh", "-c"]
command: ["if [ -x /usr/local/bin/goose ] && [ -x /usr/local/bin/river ]; then /usr/local/bin/goose -dir /app/migrations postgres \"$$DATABASE_URL\" up && /usr/local/bin/river migrate-up --database-url \"$$DATABASE_URL\"; else echo 'Migration tools (goose/river) not in image, skipping migrations.'; fi"]
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable
depends_on:
postgres:
condition: service_healthy
# Formbricks Hub API (ghcr.io/formbricks/hub). Shares the same Postgres database as Formbricks by default.
hub:
image: ghcr.io/formbricks/hub:latest
depends_on:
hub-migrate:
condition: service_completed_successfully
ports:
- "8080:8080"
environment:
API_KEY: ${HUB_API_KEY:-dev-api-key}
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable
# Explicit Postgres env so migrations and any libpq fallback use the service host, not localhost
PGHOST: postgres
PGPORT: "5432"
PGUSER: postgres
PGPASSWORD: postgres
PGDATABASE: postgres
PGSSLMODE: disable
volumes:
postgres:
driver: local
+10
View File
@@ -27,3 +27,13 @@ The script will prompt you for the following information:
3. **Domain Name**: Enter the domain name that Traefik will use to create the SSL certificate and forward requests to Formbricks.
That's it! After running the command and providing the required information, visit the domain name you entered, and you should see the Formbricks home wizard!
## Formbricks Hub
The stack includes the [Formbricks Hub](https://github.com/formbricks/hub) API (`ghcr.io/formbricks/hub`). Hub shares the same database as Formbricks by default.
- **Migrations**: A `hub-migrate` service runs Hubs database migrations (goose + river) before the Hub API starts. It runs on every `docker compose up` and is idempotent.
- **Production** (`docker/docker-compose.yml`): Set `HUB_API_KEY` (required). Override `HUB_DATABASE_URL` only if you want Hub to use a separate database (default is the same Formbricks Postgres URL).
- **Development** (`docker-compose.dev.yml`): Hub uses the same Postgres database; `API_KEY` defaults to `dev-api-key` (override with `HUB_API_KEY`).
Hub listens on port **8080**.
+34 -1
View File
@@ -29,6 +29,12 @@ x-environment: &environment
# To use external Redis/Valkey: remove the redis service below and update this URL
REDIS_URL: redis://redis:6379
# Formbricks Hub (port 8080): API key required. Use e.g. openssl rand -hex 32
HUB_API_KEY:
# Hub database URL (optional). Default: same Postgres as Formbricks. Set only if Hub uses a separate DB.
# HUB_DATABASE_URL:
# Set the minimum log level(debug, info, warn, error, fatal)
# LOG_LEVEL: info
@@ -202,7 +208,7 @@ x-environment: &environment
services:
postgres:
restart: always
image: pgvector/pgvector:pg17
image: pgvector/pgvector:pg18
volumes:
- postgres:/var/lib/postgresql/data
environment:
@@ -245,6 +251,33 @@ services:
- ./saml-connection:/home/nextjs/apps/web/saml-connection
<<: *environment
# Run Hub DB migrations (goose + river) before the API starts. Uses same image; migrations are idempotent.
hub-migrate:
image: ghcr.io/formbricks/hub:latest
restart: "no"
entrypoint: ["sh", "-c"]
command: ["if [ -x /usr/local/bin/goose ] && [ -x /usr/local/bin/river ]; then /usr/local/bin/goose -dir /app/migrations postgres \"$$DATABASE_URL\" up && /usr/local/bin/river migrate-up --database-url \"$$DATABASE_URL\"; else echo 'Migration tools (goose/river) not in image, skipping migrations.'; fi"]
environment:
DATABASE_URL: ${HUB_DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/formbricks?sslmode=disable}
depends_on:
postgres:
condition: service_healthy
# Formbricks Hub API (ghcr.io/formbricks/hub). Set HUB_API_KEY. By default shares the Formbricks database; set HUB_DATABASE_URL to use a separate DB.
hub:
restart: always
image: ghcr.io/formbricks/hub:latest
depends_on:
hub-migrate:
condition: service_completed_successfully
postgres:
condition: service_healthy
ports:
- "8080:8080"
environment:
API_KEY: ${HUB_API_KEY:?HUB_API_KEY is required to run Hub}
DATABASE_URL: ${HUB_DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/formbricks?sslmode=disable}
volumes:
postgres:
driver: local
@@ -77,4 +77,13 @@ These variables are present inside your machine's docker-compose file. Restart t
| AUDIT_LOG_ENABLED | Set this to 1 to enable audit logging. Requires Redis to be configured with the REDIS_URL env variable. | optional | 0 |
| AUDIT_LOG_GET_USER_IP | Set to 1 to include user IP addresses in audit logs from request headers | optional | 0 |
#### Formbricks Hub
When running the stack with [Formbricks Hub](https://github.com/formbricks/hub) (e.g. via docker-compose), the following variables apply to the Hub service:
| Variable | Description | Required | Default |
| ---------------- | ------------------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------- |
| HUB_API_KEY | API key for the Formbricks Hub API (port 8080). Required when the Hub service is run. | required (when Hub runs) | (e.g. `openssl rand -hex 32`) |
| HUB_DATABASE_URL | PostgreSQL connection URL for Hub. Omit to use the same database as Formbricks. | optional | Same as Formbricks `DATABASE_URL` (shared database) |
Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Github Discussions and we'll try our best to work out a solution with you.