From 73dca9d57f9e2a3216e02b7b48134b4bafb16107 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Tue, 13 Oct 2026 15:52:38 +0530 Subject: [PATCH 01/12] feat(docker): add optional experimentation profile to docker compose --- docker-compose.yml | 130 ++++++++++++++++++++++++++ experimentation/clickhouse/events.sql | 26 ++++++ 2 files changed, 156 insertions(+) create mode 100644 experimentation/clickhouse/events.sql diff --git a/docker-compose.yml b/docker-compose.yml index 464c81ca4808..2e7dc8453827 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,8 +33,14 @@ x-flagsmith-env: &flagsmith-env # EMAIL_PORT: 587 # optional # EMAIL_USE_TLS: 'true' # optional + # Experimentation needs the services in the `experimentation` profile. To enable it, run: + # EXPERIMENTATION_ENABLED=true docker compose --profile experimentation up -d + EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-false} + EXPERIMENTATION_CLICKHOUSE_URL: clickhouse://flagsmith:password@clickhouse:9000/flagsmith_exp + volumes: pgdata: + clickhouse-data: services: postgres: @@ -88,3 +94,127 @@ services: migrate-db: condition: service_completed_successfully command: run-task-processor + + # The services below run only with the `experimentation` profile. + kafka: + image: apache/kafka:3.9.1 + profiles: [experimentation] + environment: + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + healthcheck: + test: ['CMD-SHELL', '/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092'] + interval: 5s + timeout: 10s + retries: 20 + + kafka-init: + image: apache/kafka:3.9.1 + profiles: [experimentation] + entrypoint: ['/bin/sh', '-c'] + command: + - | + for topic in flagsmith_warehouse_events external_warehouse_events external_warehouse_events_retry; do + /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9092 --create --if-not-exists --topic $$topic + done + depends_on: + kafka: + condition: service_healthy + + clickhouse: + image: clickhouse/clickhouse-server:25.12 + profiles: [experimentation] + environment: + CLICKHOUSE_USER: flagsmith + CLICKHOUSE_PASSWORD: password + ulimits: + nofile: + soft: 262144 + hard: 262144 + volumes: + - clickhouse-data:/var/lib/clickhouse + healthcheck: + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8123/ping'] + interval: 5s + timeout: 3s + retries: 20 + + # Creates the events table from experimentation/clickhouse/events.sql, and has + # ClickHouse consume events from Kafka into it. + clickhouse-init: + image: clickhouse/clickhouse-server:25.12 + profiles: [experimentation] + entrypoint: ['/bin/sh', '-c'] + command: + - | + set -e + client="clickhouse-client --host clickhouse --user flagsmith --password password --multiquery" + wget -qO- https://raw.githubusercontent.com/Flagsmith/flagsmith/main/experimentation/clickhouse/events.sql | $$client + $$client <<'SQL' + CREATE TABLE IF NOT EXISTS flagsmith_exp.events_queue (raw String) + ENGINE = Kafka + SETTINGS kafka_broker_list = 'kafka:9092', + kafka_topic_list = 'flagsmith_warehouse_events', + kafka_group_name = 'clickhouse', + kafka_format = 'JSONAsString'; + + CREATE MATERIALIZED VIEW IF NOT EXISTS flagsmith_exp.events_mv TO flagsmith_exp.events AS + SELECT + JSONExtractString(raw, 'environment_key') AS environment_key, + JSONExtractString(raw, 'event') AS event, + JSONExtractString(raw, 'feature_name') AS feature_name, + fromUnixTimestamp64Milli(JSONExtract(raw, 'timestamp', 'Int64')) AS timestamp, + fromUnixTimestamp64Milli(JSONExtract(raw, 'collected_at', 'Int64')) AS collected_at, + JSONExtractString(raw, 'identifier') AS identifier, + if(JSONType(raw, 'value') = 'String', JSONExtractString(raw, 'value'), JSONExtractRaw(raw, 'value')) AS value, + JSONExtractString(raw, 'traits') AS traits, + JSONExtractString(raw, 'metadata') AS metadata, + JSONExtractString(raw, 'sdk_language') AS sdk_language, + JSONExtractString(raw, 'sdk_version') AS sdk_version + FROM flagsmith_exp.events_queue; + SQL + depends_on: + clickhouse: + condition: service_healthy + kafka-init: + condition: service_completed_successfully + + ingestion-server: + image: flagsmith/events-ingestion-api:latest + profiles: [experimentation] + environment: + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + KAFKA_AUTH: none + KAFKA_TOPIC: flagsmith_warehouse_events + KAFKA_EXTERNAL_TOPIC: external_warehouse_events + DATABASE_URL: postgres://postgres:password@postgres:5432/flagsmith + ports: + - 8080:8080 + depends_on: + migrate-db: + condition: service_completed_successfully + kafka-init: + condition: service_completed_successfully + + warehouse-delivery: + image: flagsmith/warehouse-delivery:latest + profiles: [experimentation] + environment: + KAFKA_BOOTSTRAP_SERVERS: kafka:9092 + KAFKA_AUTH: none + DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith + WAREHOUSE_CREDENTIALS_SECRET: secret # Must match the API's DJANGO_SECRET_KEY + depends_on: + migrate-db: + condition: service_completed_successfully + kafka-init: + condition: service_completed_successfully diff --git a/experimentation/clickhouse/events.sql b/experimentation/clickhouse/events.sql new file mode 100644 index 000000000000..0c71e1c8a02c --- /dev/null +++ b/experimentation/clickhouse/events.sql @@ -0,0 +1,26 @@ +-- ClickHouse schema for experimentation events. +CREATE DATABASE IF NOT EXISTS flagsmith_exp; + +CREATE TABLE IF NOT EXISTS flagsmith_exp.events +( + environment_key LowCardinality(String), + event LowCardinality(String), + feature_name LowCardinality(String), + timestamp DateTime64(3), + collected_at DateTime64(3), + identifier String, + value String CODEC(ZSTD(3)), + traits String CODEC(ZSTD(3)), + metadata String CODEC(ZSTD(3)), + sdk_language LowCardinality(String), + sdk_version LowCardinality(String), + + INDEX idx_identity identifier TYPE bloom_filter GRANULARITY 4, + + CONSTRAINT environment_key_not_empty CHECK environment_key != '', + CONSTRAINT event_not_empty CHECK event != '', + CONSTRAINT timestamp_sane CHECK timestamp > toDateTime64('2020-01-01 00:00:00', 3) +) +ENGINE = MergeTree +PARTITION BY toYYYYMMDD(timestamp) +ORDER BY (environment_key, event, feature_name, timestamp, identifier); From d8ca5d5e6dedb6fa449722b128677cf72b404381 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Tue, 13 Oct 2026 16:05:24 +0530 Subject: [PATCH 02/12] feat(docker): run experimentation services by default --- docker-compose.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2e7dc8453827..692a558709cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,9 +33,9 @@ x-flagsmith-env: &flagsmith-env # EMAIL_PORT: 587 # optional # EMAIL_USE_TLS: 'true' # optional - # Experimentation needs the services in the `experimentation` profile. To enable it, run: - # EXPERIMENTATION_ENABLED=true docker compose --profile experimentation up -d - EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-false} + # To run without experimentation and its services, run: + # EXPERIMENTATION_ENABLED=false docker compose up -d + EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-true} EXPERIMENTATION_CLICKHOUSE_URL: clickhouse://flagsmith:password@clickhouse:9000/flagsmith_exp volumes: @@ -95,10 +95,10 @@ services: condition: service_completed_successfully command: run-task-processor - # The services below run only with the `experimentation` profile. + # Experimentation services. Setting EXPERIMENTATION_ENABLED to any value (even 'true') skips them. kafka: image: apache/kafka:3.9.1 - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller @@ -119,7 +119,7 @@ services: kafka-init: image: apache/kafka:3.9.1 - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] entrypoint: ['/bin/sh', '-c'] command: - | @@ -132,7 +132,7 @@ services: clickhouse: image: clickhouse/clickhouse-server:25.12 - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: CLICKHOUSE_USER: flagsmith CLICKHOUSE_PASSWORD: password @@ -152,7 +152,7 @@ services: # ClickHouse consume events from Kafka into it. clickhouse-init: image: clickhouse/clickhouse-server:25.12 - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] entrypoint: ['/bin/sh', '-c'] command: - | @@ -190,7 +190,7 @@ services: ingestion-server: image: flagsmith/events-ingestion-api:latest - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 KAFKA_AUTH: none @@ -207,7 +207,7 @@ services: warehouse-delivery: image: flagsmith/warehouse-delivery:latest - profiles: [experimentation] + profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 KAFKA_AUTH: none From 0d72a32dec46823cf899c2f4510e688c8d71c616 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Wed, 14 Oct 2026 10:13:19 +0530 Subject: [PATCH 03/12] feat(docker): run flags only by naming services, add make targets --- Makefile | 12 ++++++++++++ docker-compose.yml | 10 ++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index c21f8c838e7c..345c0618787c 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,15 @@ lint: $(MAKE) -C api lint $(MAKE) -C docs lint $(MAKE) -C frontend lint + +.PHONY: docker-up +docker-up: + docker compose up -d + +.PHONY: docker-up-flags-only +docker-up-flags-only: + EXPERIMENTATION_ENABLED=false docker compose up -d flagsmith flagsmith-task-processor + +.PHONY: docker-down +docker-down: + docker compose down diff --git a/docker-compose.yml b/docker-compose.yml index 692a558709cc..5db3dd679a63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ x-flagsmith-env: &flagsmith-env # EMAIL_USE_TLS: 'true' # optional # To run without experimentation and its services, run: - # EXPERIMENTATION_ENABLED=false docker compose up -d + # EXPERIMENTATION_ENABLED=false docker compose up -d flagsmith flagsmith-task-processor EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-true} EXPERIMENTATION_CLICKHOUSE_URL: clickhouse://flagsmith:password@clickhouse:9000/flagsmith_exp @@ -95,10 +95,9 @@ services: condition: service_completed_successfully command: run-task-processor - # Experimentation services. Setting EXPERIMENTATION_ENABLED to any value (even 'true') skips them. + # Experimentation services. kafka: image: apache/kafka:3.9.1 - profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller @@ -119,7 +118,6 @@ services: kafka-init: image: apache/kafka:3.9.1 - profiles: ['${EXPERIMENTATION_ENABLED:-}'] entrypoint: ['/bin/sh', '-c'] command: - | @@ -132,7 +130,6 @@ services: clickhouse: image: clickhouse/clickhouse-server:25.12 - profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: CLICKHOUSE_USER: flagsmith CLICKHOUSE_PASSWORD: password @@ -152,7 +149,6 @@ services: # ClickHouse consume events from Kafka into it. clickhouse-init: image: clickhouse/clickhouse-server:25.12 - profiles: ['${EXPERIMENTATION_ENABLED:-}'] entrypoint: ['/bin/sh', '-c'] command: - | @@ -190,7 +186,6 @@ services: ingestion-server: image: flagsmith/events-ingestion-api:latest - profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 KAFKA_AUTH: none @@ -207,7 +202,6 @@ services: warehouse-delivery: image: flagsmith/warehouse-delivery:latest - profiles: ['${EXPERIMENTATION_ENABLED:-}'] environment: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 KAFKA_AUTH: none From 325371cfa33fc3d2f011fafef2be1763fea39834 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Wed, 14 Oct 2026 12:10:42 +0530 Subject: [PATCH 04/12] fix(docker): stop clickhouse-init when the schema download fails --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5db3dd679a63..98176a3166bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -154,7 +154,8 @@ services: - | set -e client="clickhouse-client --host clickhouse --user flagsmith --password password --multiquery" - wget -qO- https://raw.githubusercontent.com/Flagsmith/flagsmith/main/experimentation/clickhouse/events.sql | $$client + wget -qO /tmp/events.sql https://raw.githubusercontent.com/Flagsmith/flagsmith/main/experimentation/clickhouse/events.sql + $$client < /tmp/events.sql $$client <<'SQL' CREATE TABLE IF NOT EXISTS flagsmith_exp.events_queue (raw String) ENGINE = Kafka From 89887d569080f61f2dd435aaf5c793cd0d90a797 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Wed, 14 Oct 2026 12:23:24 +0530 Subject: [PATCH 05/12] docs(experimentation): render the ClickHouse schema from events.sql --- .../experimentation/connect-a-warehouse.md | 31 +++---------------- docs/docusaurus.config.ts | 7 +++++ 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/docs/docs/experimentation/connect-a-warehouse.md b/docs/docs/experimentation/connect-a-warehouse.md index ad8fa891c2f0..88a31945a47c 100644 --- a/docs/docs/experimentation/connect-a-warehouse.md +++ b/docs/docs/experimentation/connect-a-warehouse.md @@ -5,6 +5,8 @@ sidebar_position: 2 description: Enable the managed Flagsmith warehouse to store and query experiment events. --- +import CodeBlock from '@theme/CodeBlock'; import eventsSql from '../../../experimentation/clickhouse/events.sql?raw'; + :::info Enterprise beta Experimentation is in beta on **Enterprise** plans. [Get in touch](https://www.flagsmith.com/contact-us) to join. @@ -58,34 +60,9 @@ Instead of the managed Flagsmith warehouse, you can store experiment events in y `CREATE USER`, `GRANT`, `CREATE DATABASE` and `CREATE TABLE` privileges). Replace `` and `` with your own values. This is a one-off setup step; the same SQL is available to copy from the Flagsmith dashboard. - ```sql - -- Create the database and events table - CREATE DATABASE IF NOT EXISTS flagsmith_exp; - - CREATE TABLE IF NOT EXISTS flagsmith_exp.events - ( - environment_key LowCardinality(String), - event LowCardinality(String), - feature_name LowCardinality(String), - timestamp DateTime64(3), - collected_at DateTime64(3), - identifier String, - value String CODEC(ZSTD(3)), - traits String CODEC(ZSTD(3)), - metadata String CODEC(ZSTD(3)), - sdk_language LowCardinality(String), - sdk_version LowCardinality(String), - - INDEX idx_identity identifier TYPE bloom_filter GRANULARITY 4, - - CONSTRAINT environment_key_not_empty CHECK environment_key != '', - CONSTRAINT event_not_empty CHECK event != '', - CONSTRAINT timestamp_sane CHECK timestamp > toDateTime64('2020-01-01 00:00:00', 3) - ) - ENGINE = MergeTree - PARTITION BY toYYYYMMDD(timestamp) - ORDER BY (environment_key, event, feature_name, timestamp, identifier); + {eventsSql} + ```sql -- Create a dedicated user for Flagsmith CREATE USER IF NOT EXISTS IDENTIFIED WITH sha256_password BY ''; diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index af1287e18695..a78746d2626c 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -285,6 +285,13 @@ const config: Config = { plugins: [ './plugins/flagsmith-versions', + // Lets pages import a file's contents as text, e.g. `import sql from '../file.sql?raw'`. + () => ({ + name: 'raw-source', + configureWebpack: () => ({ + module: { rules: [{ resourceQuery: /raw/, type: 'asset/source' }] }, + }), + }), [ 'docusaurus-plugin-openapi-docs', { From 7124ae9badf72ec236be563d7f2b7af5802e2904 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Wed, 14 Oct 2026 13:16:52 +0530 Subject: [PATCH 06/12] feat(docker): deliver experimentation events through warehouse-delivery --- docker-compose.yml | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 98176a3166bb..20476ea47432 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,6 @@ x-flagsmith-env: &flagsmith-env # To run without experimentation and its services, run: # EXPERIMENTATION_ENABLED=false docker compose up -d flagsmith flagsmith-task-processor EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-true} - EXPERIMENTATION_CLICKHOUSE_URL: clickhouse://flagsmith:password@clickhouse:9000/flagsmith_exp volumes: pgdata: @@ -128,6 +127,8 @@ services: kafka: condition: service_healthy + # Connect it from Environment Settings > Warehouse > ClickHouse: host `clickhouse`, port 8123, + # TLS off, database `flagsmith_exp`, username `flagsmith`, password `password`. clickhouse: image: clickhouse/clickhouse-server:25.12 environment: @@ -145,45 +146,18 @@ services: timeout: 3s retries: 20 - # Creates the events table from experimentation/clickhouse/events.sql, and has - # ClickHouse consume events from Kafka into it. + # Creates the events table from experimentation/clickhouse/events.sql. clickhouse-init: image: clickhouse/clickhouse-server:25.12 entrypoint: ['/bin/sh', '-c'] command: - | set -e - client="clickhouse-client --host clickhouse --user flagsmith --password password --multiquery" wget -qO /tmp/events.sql https://raw.githubusercontent.com/Flagsmith/flagsmith/main/experimentation/clickhouse/events.sql - $$client < /tmp/events.sql - $$client <<'SQL' - CREATE TABLE IF NOT EXISTS flagsmith_exp.events_queue (raw String) - ENGINE = Kafka - SETTINGS kafka_broker_list = 'kafka:9092', - kafka_topic_list = 'flagsmith_warehouse_events', - kafka_group_name = 'clickhouse', - kafka_format = 'JSONAsString'; - - CREATE MATERIALIZED VIEW IF NOT EXISTS flagsmith_exp.events_mv TO flagsmith_exp.events AS - SELECT - JSONExtractString(raw, 'environment_key') AS environment_key, - JSONExtractString(raw, 'event') AS event, - JSONExtractString(raw, 'feature_name') AS feature_name, - fromUnixTimestamp64Milli(JSONExtract(raw, 'timestamp', 'Int64')) AS timestamp, - fromUnixTimestamp64Milli(JSONExtract(raw, 'collected_at', 'Int64')) AS collected_at, - JSONExtractString(raw, 'identifier') AS identifier, - if(JSONType(raw, 'value') = 'String', JSONExtractString(raw, 'value'), JSONExtractRaw(raw, 'value')) AS value, - JSONExtractString(raw, 'traits') AS traits, - JSONExtractString(raw, 'metadata') AS metadata, - JSONExtractString(raw, 'sdk_language') AS sdk_language, - JSONExtractString(raw, 'sdk_version') AS sdk_version - FROM flagsmith_exp.events_queue; - SQL + clickhouse-client --host clickhouse --user flagsmith --password password --multiquery < /tmp/events.sql depends_on: clickhouse: condition: service_healthy - kafka-init: - condition: service_completed_successfully ingestion-server: image: flagsmith/events-ingestion-api:latest @@ -213,3 +187,5 @@ services: condition: service_completed_successfully kafka-init: condition: service_completed_successfully + clickhouse-init: + condition: service_completed_successfully From 1a17d54170ac1d07696e862d6ae6fb9999eff506 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 15 Oct 2026 15:20:23 +0530 Subject: [PATCH 07/12] feat(docker): add flags-only compose file and document experimentation containers --- Makefile | 2 +- docker-compose.flags-only.yml | 90 +++++++++++++++++++ docker-compose.yml | 5 +- .../hosting-guides/docker.md | 17 ++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 docker-compose.flags-only.yml diff --git a/Makefile b/Makefile index 345c0618787c..2a24309afb58 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ docker-up: .PHONY: docker-up-flags-only docker-up-flags-only: - EXPERIMENTATION_ENABLED=false docker compose up -d flagsmith flagsmith-task-processor + docker compose -f docker-compose.flags-only.yml up -d .PHONY: docker-down docker-down: diff --git a/docker-compose.flags-only.yml b/docker-compose.flags-only.yml new file mode 100644 index 000000000000..f0dd480b6c97 --- /dev/null +++ b/docker-compose.flags-only.yml @@ -0,0 +1,90 @@ +# See https://docs.flagsmith.com/deployment/docker for more information on running Flagsmith in Docker +# This Docker Compose file will run the Flagsmith Platform without experimentation + +x-flagsmith-env: &flagsmith-env + # All environments variables are available here: + # API: https://docs.flagsmith.com/deployment/locally-api#environment-variables + # UI: https://docs.flagsmith.com/deployment/locally-frontend#environment-variables + + DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith + USE_POSTGRES_FOR_ANALYTICS: 'true' # Store API and Flag Analytics data in Postgres + ENVIRONMENT: production # set to 'production' in production. + DJANGO_ALLOWED_HOSTS: '*' # Change this in production + FLAGSMITH_DOMAIN: localhost:8000 # Change this in production + DJANGO_SECRET_KEY: secret # Change this in production + # PREVENT_SIGNUP: 'true' # Uncomment to prevent any additional signups + # ALLOW_REGISTRATION_WITHOUT_INVITE: 'true' # Uncomment and set to false to only allow signups via invitations + + # Enable Task Processor + TASK_RUN_METHOD: TASK_PROCESSOR # other options are: SYNCHRONOUSLY, SEPARATE_THREAD (default) + PROMETHEUS_ENABLED: 'true' + + # Uncomment if you want to enable Google OAuth. Note this does not turn Google OAuth on. You still need to use + # Flagsmith on Flagsmith to enable it - https://docs.flagsmith.com/deployment/#oauth_google + # DJANGO_SECURE_CROSS_ORIGIN_OPENER_POLICY: 'same-origin-allow-popups' + + # For more info on configuring E-Mails - https://docs.flagsmith.com/deployment/locally-api#environment-variables + # Example SMTP: + # EMAIL_BACKEND: django.core.mail.backends.smtp.EmailBackend + # EMAIL_HOST: mail.example.com + # SENDER_EMAIL: flagsmith@example.com + # EMAIL_HOST_USER: flagsmith@example.com + # EMAIL_HOST_PASSWORD: smtp_account_password + # EMAIL_PORT: 587 # optional + # EMAIL_USE_TLS: 'true' # optional + +volumes: + pgdata: + +services: + postgres: + image: postgres:15.5-alpine + environment: + POSTGRES_PASSWORD: password + POSTGRES_DB: flagsmith + container_name: flagsmith_postgres + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -d flagsmith -U postgres'] + interval: 2s + timeout: 2s + retries: 20 + start_period: 20s + + migrate-db: + image: docker.flagsmith.com/flagsmith/flagsmith:latest + command: + - migrate + environment: + <<: *flagsmith-env + healthcheck: + disable: true + depends_on: + postgres: + condition: service_healthy + + flagsmith: + image: docker.flagsmith.com/flagsmith/flagsmith:latest + command: + - serve + environment: + <<: *flagsmith-env + ports: + - 8000:8000 + depends_on: + migrate-db: + condition: service_completed_successfully + + # The flagsmith_processor service is only needed if TASK_RUN_METHOD set to TASK_PROCESSOR + # in the application environment + flagsmith-task-processor: + image: docker.flagsmith.com/flagsmith/flagsmith:latest + environment: + <<: *flagsmith-env + ports: + - 8001:8000 + depends_on: + migrate-db: + condition: service_completed_successfully + command: run-task-processor diff --git a/docker-compose.yml b/docker-compose.yml index 20476ea47432..bd68673fadee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,9 +33,8 @@ x-flagsmith-env: &flagsmith-env # EMAIL_PORT: 587 # optional # EMAIL_USE_TLS: 'true' # optional - # To run without experimentation and its services, run: - # EXPERIMENTATION_ENABLED=false docker compose up -d flagsmith flagsmith-task-processor - EXPERIMENTATION_ENABLED: ${EXPERIMENTATION_ENABLED:-true} + # To run without experimentation, use docker-compose.flags-only.yml instead. + EXPERIMENTATION_ENABLED: 'true' volumes: pgdata: diff --git a/docs/docs/deployment-self-hosting/hosting-guides/docker.md b/docs/docs/deployment-self-hosting/hosting-guides/docker.md index 4010f66ed928..11cfbe05e6e9 100644 --- a/docs/docs/deployment-self-hosting/hosting-guides/docker.md +++ b/docs/docs/deployment-self-hosting/hosting-guides/docker.md @@ -16,6 +16,13 @@ docker-compose -f docker-compose.yml up Wait for the images to download and run, then visit `http://localhost:8000/`. As a first step, you will need to create a new account at [http://localhost:8000/signup](http://localhost:8000/signup) +To run Flagsmith without [experimentation](#experimentation), use the flags-only file instead: + +```bash +curl -o docker-compose.yml https://raw.githubusercontent.com/Flagsmith/flagsmith/main/docker-compose.flags-only.yml +docker-compose -f docker-compose.yml up +``` + ## Environment Variables As well as the Environment Variables specified in the [API](/deployment-self-hosting/core-configuration/environment-variables#api-environment-variables) and [Frontend](/deployment-self-hosting/core-configuration/environment-variables#frontend-environment-variables), you can also specify the following: @@ -60,6 +67,16 @@ You can then access the admin dashboard at [http://localhost:8000/admin/](http:/ The REST API stores all its data within a PostgreSQL database. Schema changes will be carried out automatically when upgrading using Django Migrations. +### Experimentation + +These containers run [experimentation](/experimentation/connect-a-warehouse): + +- **Ingestion server (port 8080)**: receives exposure and conversion events from the SDKs. +- **Kafka**: queues events between the ingestion server and warehouse delivery. +- **Warehouse delivery**: writes events to each environment's connected warehouse. +- **ClickHouse**: a warehouse you can connect from **Environment Settings > Warehouse**, using host `clickhouse`, port + `8123`, TLS off, database `flagsmith_exp`, username `flagsmith` and password `password`. + ## Access Flagsmith Remotely You will need to either open ports into your Docker host or set up a reverse proxy to access the two Flagsmith services (dashboard and API). You will also need to configure the dashboard environment variable `API_URL`, which tells the dashboard where the REST API is located. From 2c2ab9d565c0a6116cffc9e6deee814e573848d2 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 15 Oct 2026 15:26:08 +0530 Subject: [PATCH 08/12] chore: drop docker make targets --- Makefile | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Makefile b/Makefile index 2a24309afb58..c21f8c838e7c 100644 --- a/Makefile +++ b/Makefile @@ -17,15 +17,3 @@ lint: $(MAKE) -C api lint $(MAKE) -C docs lint $(MAKE) -C frontend lint - -.PHONY: docker-up -docker-up: - docker compose up -d - -.PHONY: docker-up-flags-only -docker-up-flags-only: - docker compose -f docker-compose.flags-only.yml up -d - -.PHONY: docker-down -docker-down: - docker compose down From 2acc9f66fcffddcdd71dc6bf31345bbc20b76487 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Thu, 15 Oct 2026 15:53:17 +0530 Subject: [PATCH 09/12] chore: add docker-up and docker-down make targets --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Makefile b/Makefile index c21f8c838e7c..37de04d6a6cc 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,14 @@ lint: $(MAKE) -C api lint $(MAKE) -C docs lint $(MAKE) -C frontend lint + +# Run `make docker-up flags-only=1` to run Flagsmith without experimentation. +compose-file = $(if $(flags-only),docker-compose.flags-only.yml,docker-compose.yml) + +.PHONY: docker-up +docker-up: + docker compose -f $(compose-file) up -d + +.PHONY: docker-down +docker-down: + docker compose -f $(compose-file) down From 56d3954b7baa4da2583f51d2df5ad589d1a02793 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Fri, 16 Oct 2026 09:08:00 +0530 Subject: [PATCH 10/12] feat(docker): share WAREHOUSE_CREDENTIALS_SECRET between the API and warehouse delivery --- docker-compose.yml | 3 ++- .../core-configuration/environment-variables.md | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index bd68673fadee..f1064ea0b2ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ x-flagsmith-env: &flagsmith-env DJANGO_ALLOWED_HOSTS: '*' # Change this in production FLAGSMITH_DOMAIN: localhost:8000 # Change this in production DJANGO_SECRET_KEY: secret # Change this in production + WAREHOUSE_CREDENTIALS_SECRET: &warehouse-credentials-secret secret # Change this in production # PREVENT_SIGNUP: 'true' # Uncomment to prevent any additional signups # ALLOW_REGISTRATION_WITHOUT_INVITE: 'true' # Uncomment and set to false to only allow signups via invitations @@ -180,7 +181,7 @@ services: KAFKA_BOOTSTRAP_SERVERS: kafka:9092 KAFKA_AUTH: none DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith - WAREHOUSE_CREDENTIALS_SECRET: secret # Must match the API's DJANGO_SECRET_KEY + WAREHOUSE_CREDENTIALS_SECRET: *warehouse-credentials-secret depends_on: migrate-db: condition: service_completed_successfully diff --git a/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md index f87dd5f94f89..07441b49e778 100644 --- a/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md +++ b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md @@ -49,6 +49,8 @@ relevant section below for more details. - `INFLUXDB_ORG`: The organisation string for your InfluxDB API call. - `EXPERIMENTATION_ENABLED`: Set to `true` once the experimentation infra is deployed. Experiments and warehouse connections stay unavailable until it is. Defaults to `false`. +- `WAREHOUSE_CREDENTIALS_SECRET`: Encrypts the warehouse connection credentials stored by the API. Warehouse delivery + must be given the same value to decrypt them. Defaults to `DJANGO_SECRET_KEY`. - `GA_TABLE_ID`: GA table ID (view) to query when looking for organisation usage. - `USER_CREATE_PERMISSIONS`: Set the permissions for creating new users, using a comma-separated list of djoser or rest_framework permissions. Use this to turn off public user creation for self-hosting. e.g. From feb24ffeb6f76b0dbd5d4f229b30cf389b707a05 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Fri, 16 Oct 2026 09:09:11 +0530 Subject: [PATCH 11/12] docs: drop WAREHOUSE_CREDENTIALS_SECRET default --- .../core-configuration/environment-variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md index 07441b49e778..6a6770fb8d3e 100644 --- a/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md +++ b/docs/docs/deployment-self-hosting/core-configuration/environment-variables.md @@ -50,7 +50,7 @@ relevant section below for more details. - `EXPERIMENTATION_ENABLED`: Set to `true` once the experimentation infra is deployed. Experiments and warehouse connections stay unavailable until it is. Defaults to `false`. - `WAREHOUSE_CREDENTIALS_SECRET`: Encrypts the warehouse connection credentials stored by the API. Warehouse delivery - must be given the same value to decrypt them. Defaults to `DJANGO_SECRET_KEY`. + must be given the same value to decrypt them. - `GA_TABLE_ID`: GA table ID (view) to query when looking for organisation usage. - `USER_CREATE_PERMISSIONS`: Set the permissions for creating new users, using a comma-separated list of djoser or rest_framework permissions. Use this to turn off public user creation for self-hosting. e.g. From 81efa889f1206fcf7b86bdf99c5369151109957b Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Fri, 16 Oct 2026 14:44:08 +0530 Subject: [PATCH 12/12] test(frontend): drop ClickHouse docs copy test --- .../__tests__/clickhouseSetupSql.test.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseSetupSql.test.ts b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseSetupSql.test.ts index dee3e4b38eea..d2c7b32f439a 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseSetupSql.test.ts +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseSetupSql.test.ts @@ -1,5 +1,3 @@ -import fs from 'fs' -import path from 'path' import { CLICKHOUSE_DEFAULTS } from 'components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig' import { getClickHouseOnboardingSql, @@ -46,22 +44,4 @@ describe('getClickHouseOnboardingSql', () => { expect(createTable).toBeLessThan(sql.indexOf('CREATE USER')) expect(createTable).toBeLessThan(sql.indexOf('GRANT')) }) - - it('matches the copy of the script in the documentation', () => { - const doc = fs.readFileSync( - path.join( - __dirname, - '../../../../../../../../docs/docs/experimentation/connect-a-warehouse.md', - ), - 'utf8', - ) - const fencedSql = doc.match(/```sql\n([\s\S]*?)```/)?.[1] ?? '' - const dedented = fencedSql - .split('\n') - .map((line) => line.replace(/^ {3}/, '')) - .join('\n') - .trim() - - expect(dedented).toEqual(getClickHouseOnboardingSql()) - }) })