diff --git a/Dockerfile b/Dockerfile index 78d13c806286..6167a1fc0a81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -135,7 +135,8 @@ ARG PROMETHEUS_MULTIPROC_DIR="/tmp/prometheus" ARG ACCESS_LOG_LOCATION="/dev/null" ENV ACCESS_LOG_LOCATION=${ACCESS_LOG_LOCATION} \ PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR} \ - DJANGO_SETTINGS_MODULE=app.settings.production + DJANGO_SETTINGS_MODULE=app.settings.production \ + APPLICATION_LOGGERS="app_analytics,audit,code_references,common,core,dynamodb,edge_api,environments,features,import_export,integrations,mcp,oauth2_metadata,organisations,projects,segments,task_processor,users,webhooks,workflows" ARG CI_COMMIT_SHA RUN echo ${CI_COMMIT_SHA} > /app/CI_COMMIT_SHA && \ @@ -144,7 +145,7 @@ RUN echo ${CI_COMMIT_SHA} > /app/CI_COMMIT_SHA && \ EXPOSE 8000 -ENTRYPOINT ["/app/scripts/run-docker.sh"] +ENTRYPOINT ["flagsmith"] CMD ["migrate-and-serve"] diff --git a/api/app/settings/common.py b/api/app/settings/common.py index ba394155f34f..065c1e2c757d 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -1503,3 +1503,20 @@ }, } DATABASES["clickhouse"] = _clickhouse_db # type: ignore[assignment] + +# Startup verbs (flagsmith-common `flagsmith` entrypoint): which databases each +# role migrates and waits on. Mirrors the per-role gating run-docker.sh applied — +# only databases actually configured are included. `DATABASES` is undefined when +# no `DATABASE_URL` is set (e.g. build-time collectstatic), so guard for that. +_configured_databases = globals().get("DATABASES", {}) +FLAGSMITH_MIGRATE_DATABASES = [ + alias + for alias in ("default", "analytics", "task_processor", "clickhouse") + if alias in _configured_databases +] +FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES = [ + alias + for alias in ("default", "analytics", "task_processor") + if alias in _configured_databases +] +FLAGSMITH_STARTUP_COMMANDS = ["bootstrap"] diff --git a/api/pyproject.toml b/api/pyproject.toml index 5756b28e0c4a..fde424490a7d 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -155,6 +155,9 @@ explicit = true [tool.uv.sources] flagsmith-private = { index = "flagsmith-pypi-production" } +# TEMPORARY: track the composite startup verbs branch until flagsmith-common ships them. +# TODO: revert to the PyPI release once the entrypoint verbs are released. +flagsmith-common = { git = "https://github.com/Flagsmith/flagsmith-common", branch = "feat/composite-startup-verbs" } clickhouse-driver = { git = "https://github.com/Flagsmith/clickhouse-driver", branch = "newjson" } # TODO: Revert to the PyPI release once a version closing # https://github.com/jayvynl/django-clickhouse-backend/issues/154 is published. diff --git a/api/scripts/run-docker.sh b/api/scripts/run-docker.sh index 9d8c061ef325..07314fbffcdb 100755 --- a/api/scripts/run-docker.sh +++ b/api/scripts/run-docker.sh @@ -1,108 +1,12 @@ #!/bin/sh set -e -# common environment variables -ACCESS_LOG_FORMAT=${ACCESS_LOG_FORMAT:-'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %({origin}i)s %({access-control-allow-origin}o)s'} -APPLICATION_LOGGERS=${APPLICATION_LOGGERS:-"app_analytics,audit,code_references,common,core,dynamodb,edge_api,environments,features,import_export,integrations,mcp,oauth2_metadata,organisations,projects,segments,task_processor,users,webhooks,workflows"} - -waitfordb() { - if [ -z "${SKIP_WAIT_FOR_DB}" ]; then - python manage.py waitfordb "$@" - fi -} - -migrate () { - waitfordb \ - && python manage.py showmigrations --verbosity 2 \ - && python manage.py migrate --verbosity 2 \ - && python manage.py createcachetable -} -serve() { - # configuration parameters for statsd. Docs can be found here: - # https://docs.gunicorn.org/en/stable/instrumentation.html - export STATSD_PORT=${STATSD_PORT:-8125} - export STATSD_PREFIX=${STATSD_PREFIX:-flagsmith.api} - - waitfordb - - exec flagsmith start \ - --worker-tmp-dir /dev/shm \ - --timeout ${GUNICORN_TIMEOUT:-30} \ - --workers ${GUNICORN_WORKERS:-3} \ - --threads ${GUNICORN_THREADS:-2} \ - --access-logfile $ACCESS_LOG_LOCATION \ - --access-logformat "$ACCESS_LOG_FORMAT" \ - --keep-alive ${GUNICORN_KEEP_ALIVE:-2} \ - ${STATSD_HOST:+--statsd-host $STATSD_HOST:$STATSD_PORT} \ - ${STATSD_HOST:+--statsd-prefix $STATSD_PREFIX} \ - api -} -run_task_processor() { - waitfordb --waitfor 30 --migrations - if [ -n "$ANALYTICS_DATABASE_URL" ] || [ -n "$DJANGO_DB_NAME_ANALYTICS" ]; then - waitfordb --waitfor 30 --migrations --database analytics - fi - if [ -n "$TASK_PROCESSOR_DATABASE_URL" ] || [ -n "$TASK_PROCESSOR_DATABASE_NAME" ]; then - waitfordb --waitfor 30 --migrations --database task_processor - fi - exec flagsmith start \ - --bind 0.0.0.0:8000 \ - --access-logfile $ACCESS_LOG_LOCATION \ - --access-logformat "$ACCESS_LOG_FORMAT" \ - task-processor \ - --sleepintervalms ${TASK_PROCESSOR_SLEEP_INTERVAL_MS:-${TASK_PROCESSOR_SLEEP_INTERVAL:-500}} \ - --graceperiodms ${TASK_PROCESSOR_GRACE_PERIOD_MS:-20000} \ - --numthreads ${TASK_PROCESSOR_NUM_THREADS:-5} \ - --queuepopsize ${TASK_PROCESSOR_QUEUE_POP_SIZE:-10} - -} -migrate_analytics_db(){ - if [ -z "$ANALYTICS_DATABASE_URL" ] && [ -z "$DJANGO_DB_NAME_ANALYTICS" ]; then - return 0 - fi - python manage.py migrate --database analytics -} -migrate_task_processor_db(){ - if [ -z "$TASK_PROCESSOR_DATABASE_URL" ] && [ -z "$TASK_PROCESSOR_DATABASE_NAME" ]; then - return 0 - fi - python manage.py migrate --database task_processor -} -migrate_clickhouse_db(){ - if [ -z "$CLICKHOUSE_URL" ] && [ -z "$CLICKHOUSE_HOST" ]; then - return 0 - fi - python manage.py migrate --database clickhouse -} -bootstrap(){ - python manage.py bootstrap -} -default(){ - python manage.py "$@" -} - -set -x - -if [ "$1" = "migrate" ]; then - migrate - migrate_analytics_db - migrate_task_processor_db - migrate_clickhouse_db -elif [ "$1" = "serve" ]; then - serve -elif [ "$1" = "run-task-processor" ]; then - migrate - migrate_analytics_db - migrate_task_processor_db - migrate_clickhouse_db - run_task_processor -elif [ "$1" = "migrate-and-serve" ]; then - migrate - migrate_analytics_db - migrate_task_processor_db - migrate_clickhouse_db - bootstrap - serve -else - default "$@" -fi +# Container startup is handled by the `flagsmith` entrypoint (flagsmith-common), +# which provides the serve / migrate / run-task-processor / migrate-and-serve +# verbs this script used to implement in shell. +# +# This shim stays for backwards compatibility: anything invoking the script +# path directly (Helm charts, compose files, task definitions) keeps working. +# Prefer calling `flagsmith ` directly; this file can be removed once all +# consumers have migrated. +exec flagsmith "$@" diff --git a/api/uv.lock b/api/uv.lock index 4cdafbef31c9..93e1e7eea15d 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1552,8 +1552,8 @@ requires-dist = [ { name = "email-validator", marker = "extra == 'dev'", specifier = ">=2.0.0" }, { name = "environs", specifier = ">=14.1.1,<15.0.0" }, { name = "flagsmith", specifier = ">=5.3.0,<6.0.0" }, - { name = "flagsmith-common", extras = ["common-core", "flagsmith-schemas", "task-processor"], specifier = ">=3.9.1,<4" }, - { name = "flagsmith-common", extras = ["test-tools"], marker = "extra == 'dev'" }, + { name = "flagsmith-common", extras = ["common-core", "flagsmith-schemas", "task-processor"], git = "https://github.com/Flagsmith/flagsmith-common?branch=feat%2Fcomposite-startup-verbs" }, + { name = "flagsmith-common", extras = ["test-tools"], marker = "extra == 'dev'", git = "https://github.com/Flagsmith/flagsmith-common?branch=feat%2Fcomposite-startup-verbs" }, { name = "flagsmith-flag-engine", specifier = ">=10.1.0,<11.0.0" }, { name = "flagsmith-private", marker = "extra == 'private'", specifier = ">=0.10.0,<1", index = "https://flagsmith-production-084060095745.d.codeartifact.eu-west-2.amazonaws.com/pypi/flagsmith-pypi-production/simple/" }, { name = "flagsmith-sql-flag-engine", specifier = ">=0.1.0,<0.2.0" }, @@ -1623,12 +1623,8 @@ provides-extras = ["private", "dev"] [[package]] name = "flagsmith-common" -version = "3.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/02/6db44d9089832b0267f9b8bac73cf57eeb3769364e6a523112273ee5cbea/flagsmith_common-3.9.1.tar.gz", hash = "sha256:2b78015b290c571d20e2ba59ee621346cf7ec8340bfc01acf3620af3725d9318", size = 59166, upload-time = "2026-05-11T16:46:29.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/fd/67f602c3859eba1baf30fc1916d0997cb3541b71acea5964d47c2b4b5a3e/flagsmith_common-3.9.1-py3-none-any.whl", hash = "sha256:32bd530a32ecd0ff7a6e868341b60d7e778d56ebf0a7ed0623ecdfcabd87fef8", size = 96771, upload-time = "2026-05-11T16:46:28.271Z" }, -] +version = "3.10.0" +source = { git = "https://github.com/Flagsmith/flagsmith-common?branch=feat%2Fcomposite-startup-verbs#5def4092278c04c17f122abdfdbac752125feb66" } [package.optional-dependencies] common-core = [ @@ -1665,6 +1661,7 @@ task-processor = [ { name = "backoff" }, { name = "django" }, { name = "django-health-check" }, + { name = "environs" }, { name = "opentelemetry-api" }, { name = "prometheus-client" }, ] diff --git a/infrastructure/aws/production/ecs-task-definition-task-processor.json b/infrastructure/aws/production/ecs-task-definition-task-processor.json index 4c73ad144510..a476a1bf12e4 100644 --- a/infrastructure/aws/production/ecs-task-definition-task-processor.json +++ b/infrastructure/aws/production/ecs-task-definition-task-processor.json @@ -7,7 +7,8 @@ { "name": "flagsmith-task-processor", "command": [ - "run-task-processor" + "start", + "task-processor" ], "portMappings": [ { @@ -29,7 +30,7 @@ "interval": 10, "timeout": 2, "retries": 5, - "startPeriod": 120 + "startPeriod": 30 }, "essential": true, "environment": [ diff --git a/infrastructure/aws/staging/ecs-task-definition-task-processor.json b/infrastructure/aws/staging/ecs-task-definition-task-processor.json index e840904fe471..7c1ed3f07f94 100644 --- a/infrastructure/aws/staging/ecs-task-definition-task-processor.json +++ b/infrastructure/aws/staging/ecs-task-definition-task-processor.json @@ -6,7 +6,7 @@ "containerDefinitions": [ { "name": "flagsmith-task-processor", - "command": ["run-task-processor"], + "command": ["start", "task-processor"], "portMappings": [ { "containerPort": 9100, @@ -24,7 +24,7 @@ "interval": 10, "timeout": 2, "retries": 5, - "startPeriod": 120 + "startPeriod": 30 }, "essential": true, "environment": [