From 84704af59659db7e3916ad024fd4ec16fb58705a Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Sat, 27 Jun 2026 12:49:00 +0100 Subject: [PATCH 1/3] feat(api): Adopt the flagsmith entrypoint for container startup Replace the bespoke startup sequencing in run-docker.sh with the composite verbs provided by the flagsmith-common `flagsmith` entrypoint: - run-docker.sh becomes a thin `exec flagsmith "$@"` shim, kept for callers that reference the script path or its verbs directly. - The image ENTRYPOINT is now `flagsmith`; the APPLICATION_LOGGERS default the shell script used to set moves to a Dockerfile ENV. - Startup verbs are driven by new settings built from the configured databases (FLAGSMITH_MIGRATE_DATABASES, FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES, FLAGSMITH_STARTUP_COMMANDS). - The task processor task definition starts via `start task-processor` and no longer migrates on every task; migrations run once per deploy via the migration task. startPeriod drops from 120s to 30s for the faster boot. flagsmith-common is temporarily sourced from its branch until the verbs are released; revert to the PyPI release before merge (see pyproject TODO). beep boop --- Dockerfile | 5 +- api/app/settings/common.py | 17 +++ api/pyproject.toml | 3 + api/scripts/run-docker.sh | 114 ++---------------- .../ecs-task-definition-task-processor.json | 5 +- .../ecs-task-definition-task-processor.json | 4 +- 6 files changed, 37 insertions(+), 111 deletions(-) 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 53b829be4b71..ef85e208daab 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -1525,3 +1525,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 3eeab6874148..1f254ef211f4 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 4d7dec5d8d28..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,segment_membership,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/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": [ From 5ddf8e8311cc9fff73c6ff834a75b50c7dd9cb20 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 6 Jul 2026 13:59:59 +0100 Subject: [PATCH 2/3] refactor(api): Build migrate/wait DB lists at each config site Populate FLAGSMITH_MIGRATE_DATABASES and FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES as each database alias is configured, instead of reconstructing them from a _configured_databases snapshot after the fact. Bump flagsmith-common to the 3.11.0 release, which ships the composite startup verbs, and drop the temporary git source. Remove the now-redundant prose comments from run-docker.sh and the settings. beep boop --- Dockerfile | 2 +- api/app/settings/common.py | 33 ++++++++++++++++----------------- api/pyproject.toml | 5 +---- api/scripts/run-docker.sh | 8 -------- api/uv.lock | 9 +++++---- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6167a1fc0a81..465f0dd268a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -136,7 +136,7 @@ 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 \ - 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" + APPLICATION_LOGGERS="app_analytics,audit,code_references,common,core,dynamodb,edge_api,environments,features,import_export,integrations,mcp,oauth2_metadata,organisations,projects,segment_membership,segments,task_processor,users,webhooks,workflows" ARG CI_COMMIT_SHA RUN echo ${CI_COMMIT_SHA} > /app/CI_COMMIT_SHA && \ diff --git a/api/app/settings/common.py b/api/app/settings/common.py index ef85e208daab..35c2c2c5c667 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -183,6 +183,11 @@ DJANGO_DB_CONN_HEALTH_CHECKS = env.bool("DJANGO_DB_CONN_HEALTH_CHECKS", False) DATABASE_ROUTERS: list[str] = [] + +FLAGSMITH_MIGRATE_DATABASES: list[str] = [] +FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES: list[str] = [] +FLAGSMITH_STARTUP_COMMANDS = ["bootstrap"] + # Allows collectstatic to run without a database, mainly for Docker builds to collectstatic at build time if "DATABASE_URL" in os.environ: DATABASES = { @@ -192,6 +197,8 @@ conn_health_checks=DJANGO_DB_CONN_HEALTH_CHECKS, ), } + FLAGSMITH_MIGRATE_DATABASES.append("default") + FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES.append("default") REPLICA_DATABASE_URLS_DELIMITER = env("REPLICA_DATABASE_URLS_DELIMITER", ",") REPLICA_DATABASE_URLS = ( env.list( @@ -249,6 +256,8 @@ conn_health_checks=DJANGO_DB_CONN_HEALTH_CHECKS, ) DATABASE_ROUTERS.insert(0, "app.routers.AnalyticsRouter") + FLAGSMITH_MIGRATE_DATABASES.append("analytics") + FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES.append("analytics") elif "DJANGO_DB_NAME" in os.environ: # If there is no DATABASE_URL configured, check for old style DB config parameters DATABASES = { @@ -263,6 +272,8 @@ "CONN_HEALTH_CHECKS": DJANGO_DB_CONN_HEALTH_CHECKS, }, } + FLAGSMITH_MIGRATE_DATABASES.append("default") + FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES.append("default") if "DJANGO_DB_NAME_ANALYTICS" in os.environ: DATABASES["analytics"] = { "ENGINE": "django.db.backends.postgresql", @@ -276,6 +287,8 @@ } DATABASE_ROUTERS.insert(0, "app.routers.AnalyticsRouter") + FLAGSMITH_MIGRATE_DATABASES.append("analytics") + FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES.append("analytics") # Task processor database — OPTIONALLY SEPARATED TASK_PROCESSOR_DATABASE_URL = env("TASK_PROCESSOR_DATABASE_URL", default=None) @@ -305,6 +318,8 @@ "CONN_MAX_AGE": DJANGO_DB_CONN_MAX_AGE, } DATABASE_ROUTERS.insert(0, "task_processor.routers.TaskProcessorRouter") + FLAGSMITH_MIGRATE_DATABASES.append("task_processor") + FLAGSMITH_WAIT_FOR_MIGRATIONS_DATABASES.append("task_processor") # Consume any remaining tasks from 'default' when opting in to 'task_processor' database _task_processor_databases = ["default", "task_processor"] @@ -1525,20 +1540,4 @@ }, } 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"] + FLAGSMITH_MIGRATE_DATABASES.append("clickhouse") diff --git a/api/pyproject.toml b/api/pyproject.toml index 1f254ef211f4..c77b57994d47 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -74,7 +74,7 @@ dependencies = [ "hubspot-api-client>=12.0.0,<13.0.0", "djangorestframework-dataclasses>=1.3.1,<2.0.0", "pyotp>=2.9.0,<3.0.0", - "flagsmith-common[common-core,flagsmith-schemas,task-processor]>=3.10.1,<4", + "flagsmith-common[common-core,flagsmith-schemas,task-processor]>=3.11.0,<4", "django-stubs>=5.1.3,<6.0.0", "tzdata>=2024.1,<2025.0.0", "djangorestframework-simplejwt>=5.5.1,<6.0.0", @@ -155,9 +155,6 @@ 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 07314fbffcdb..9afa44cb08ff 100755 --- a/api/scripts/run-docker.sh +++ b/api/scripts/run-docker.sh @@ -1,12 +1,4 @@ #!/bin/sh set -e -# 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 aa586f711b7a..7716a2962e9e 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1552,7 +1552,7 @@ 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.10.1,<4" }, + { name = "flagsmith-common", extras = ["common-core", "flagsmith-schemas", "task-processor"], specifier = ">=3.11.0,<4" }, { name = "flagsmith-common", extras = ["test-tools"], marker = "extra == 'dev'" }, { name = "flagsmith-flag-engine", specifier = ">=10.1.0,<11.0.0" }, { name = "flagsmith-private", marker = "extra == 'private'", specifier = ">=0.10.1,<1", index = "https://flagsmith-production-084060095745.d.codeartifact.eu-west-2.amazonaws.com/pypi/flagsmith-pypi-production/simple/" }, @@ -1623,11 +1623,11 @@ provides-extras = ["private", "dev"] [[package]] name = "flagsmith-common" -version = "3.10.1" +version = "3.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/88/69b64381dd5c27eab67da364b5163bc40fc500fb45804d9ba6824a2e3d1d/flagsmith_common-3.10.1.tar.gz", hash = "sha256:d07b8bdac4e2b7913daed2538c5fec7512a08f10e6f64fc5a2bb1016f9493a7b", size = 59260, upload-time = "2026-06-30T10:06:07.926Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/10/5bcec25cfa4e86ad25fa0c97269b74633bf0305abc181c1d439a26bd9864/flagsmith_common-3.11.0.tar.gz", hash = "sha256:178f908c5355e41ad5bed8839943de28b37fcf69131fac464751a8e3d6bf3301", size = 61027, upload-time = "2026-07-06T11:51:10.344Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b1/0527d883b246b4b600d5595a1648d5bda34110747962c88377d5c2301eb0/flagsmith_common-3.10.1-py3-none-any.whl", hash = "sha256:ea03bd41c7842d00bfbf65546f371b9e3d602b1e772cb33e9e86d9bcfd732236", size = 96851, upload-time = "2026-06-30T10:06:06.324Z" }, + { url = "https://files.pythonhosted.org/packages/af/60/83c432cd1368281a483eb94a13ba789180c48d576e57222fce8e289004be/flagsmith_common-3.11.0-py3-none-any.whl", hash = "sha256:c4b637887cb22a89cc8fe7083342cc32ad8ccc5e7f8d050bd0bb1e4155a73651", size = 99115, upload-time = "2026-07-06T11:51:08.932Z" }, ] [package.optional-dependencies] @@ -1665,6 +1665,7 @@ task-processor = [ { name = "backoff" }, { name = "django" }, { name = "django-health-check" }, + { name = "environs" }, { name = "opentelemetry-api" }, { name = "prometheus-client" }, ] From 3f8b967f967a092808ff759d0c1bc7ad1f22b61e Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 6 Jul 2026 14:32:06 +0100 Subject: [PATCH 3/3] fix(api): API serves with a single gunicorn worker under the flagsmith entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flagsmith-common `serve` verb runs a bare `flagsmith start api`, whose gunicorn defaults are GUNICORN_WORKERS=1 and GUNICORN_THREADS=1. The run-docker.sh serve() it replaces defaulted to 3 workers and 2 threads, so image consumers that do not set these (self-hosted deployments and the E2E suite) drop from six concurrent request handlers to one. Restore the previous defaults as image ENV, alongside APPLICATION_LOGGERS. SaaS is unaffected — its task definitions set both explicitly. beep boop --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 465f0dd268a1..8326ad3a7097 100644 --- a/Dockerfile +++ b/Dockerfile @@ -136,6 +136,8 @@ 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 \ + GUNICORN_WORKERS=3 \ + GUNICORN_THREADS=2 \ APPLICATION_LOGGERS="app_analytics,audit,code_references,common,core,dynamodb,edge_api,environments,features,import_export,integrations,mcp,oauth2_metadata,organisations,projects,segment_membership,segments,task_processor,users,webhooks,workflows" ARG CI_COMMIT_SHA