# Your usual Flagsmith release produces of a number of shippable Docker images: # - Private cloud: API, Unified # - SaaS: API # - Open Source: API, Frontend, Unified # This Dockerfile is meant to build all of the above via composable, interdependent stages. # The goal is to have as DRY as possible build for all the targets. # Usage Examples # Build an Open Source API image: # $ docker build -t flagsmith-api:dev --target oss-api . # Build an Open Source Unified image: # (`oss-unified` stage is the default one, so there's no need to specify a target stage) # $ docker build -t flagsmith:dev . # Build a SaaS API image: # $ GH_TOKEN=$(gh auth token) docker build -t flagsmith-saas-api:dev --target saas-api \ # --secret="id=sse_pgp_pkey,src=./sse_pgp_pkey.key"\ # --secret="id=github_private_cloud_token,env=GH_TOKEN" . # Build a Private Cloud Unified image: # $ GH_TOKEN=$(gh auth token) docker build -t flagsmith-private-cloud:dev --target private-cloud-unified \ # --secret="id=github_private_cloud_token,env=GH_TOKEN" . # Table of Contents # Stages are described as stage-name [dependencies] # - Intermediary stages # * build-node [node] # * build-node-django [build-node] # * build-node-selfhosted [build-node] # * build-python [wolfi-base] # * build-python-private [build-python] # * api-runtime [wolfi-base] # * api-runtime-private [api-runtime] # - Internal stages # * api-test [build-python] # * api-private-test [build-python-private] # - Target (shippable) stages # * private-cloud-api [api-runtime-private, build-python-private] # * private-cloud-unified [api-runtime-private, build-python-private, build-node-django] # * saas-api [api-runtime-private, build-python-private] # * oss-api [api-runtime, build-python] # * oss-frontend [node:slim, build-node-selfhosted] # * oss-unified [api-runtime, build-python, build-node-django] ARG CI_COMMIT_SHA=dev # Pin runtimes versions ARG NODE_VERSION=24 ARG PYTHON_VERSION=3.13 FROM public.ecr.aws/docker/library/node:${NODE_VERSION}-bookworm AS node FROM cgr.dev/chainguard/wolfi-base:latest AS wolfi-base # - Intermediary stages # * build-node FROM node AS build-node # Copy the files required to install npm packages WORKDIR /build COPY frontend/package.json frontend/package-lock.json frontend/.npmrc ./frontend/.nvmrc ./frontend/ COPY frontend/bin/ ./frontend/bin/ COPY frontend/env/ ./frontend/env/ ARG ENV=selfhosted RUN cd frontend && ENV=${ENV} npm ci --quiet --production COPY frontend /build/frontend # * build-node-django [build-node] FROM build-node AS build-node-django RUN mkdir /build/api && cd frontend && npm run bundledjango # * build-node-selfhosted [build-node] FROM build-node AS build-node-selfhosted RUN cd frontend && npm run bundle # * build-python FROM wolfi-base AS build-python COPY --from=ghcr.io/astral-sh/uv /uv /uvx /bin/ WORKDIR /build ARG PYTHON_VERSION RUN apk add build-base linux-headers curl git \ python-${PYTHON_VERSION} \ python-${PYTHON_VERSION}-dev \ py${PYTHON_VERSION}-pip COPY api/pyproject.toml api/uv.lock api/Makefile ./ ENV UV_PROJECT_ENVIRONMENT=/build/.venv \ UV_PYTHON_PREFERENCE=only-system \ UV_PYTHON=python${PYTHON_VERSION} \ UV_LINK_MODE=copy \ UV_NO_SYNC=1 \ UV_CACHE_DIR=/root/.cache/uv RUN --mount=type=cache,target=/root/.cache/uv \ make install opts='--no-install-project' # * build-python-private [build-python] FROM build-python AS build-python-private # Authenticate git with token and install private Python dependencies ARG EXTRAS="--extra private" RUN --mount=type=secret,id=github_private_cloud_token \ --mount=type=secret,id=codeartifact_token \ --mount=type=cache,target=/root/.cache/uv \ echo "https://$(cat /run/secrets/github_private_cloud_token):@github.com" > ${HOME}/.git-credentials && \ git config --global credential.helper store && \ UV_INDEX_FLAGSMITH_PYPI_PRODUCTION_USERNAME=aws \ UV_INDEX_FLAGSMITH_PYPI_PRODUCTION_PASSWORD="$(cat /run/secrets/codeartifact_token)" \ make install-packages opts="--no-install-project ${EXTRAS}" # * api-runtime FROM wolfi-base AS api-runtime # Install Python and make it available to venv entrypoints ARG PYTHON_VERSION RUN apk add python-${PYTHON_VERSION} && \ mkdir /build/ && ln -s /usr/local/ /build/.venv WORKDIR /app COPY api /app/ COPY .release-please-manifest.json /app/.versions.json 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 \ 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,trust_relationships,users,webhooks,workflows" ARG CI_COMMIT_SHA RUN echo ${CI_COMMIT_SHA} > /app/CI_COMMIT_SHA && \ mkdir -p ${PROMETHEUS_MULTIPROC_DIR} && \ chmod 01777 ${PROMETHEUS_MULTIPROC_DIR} EXPOSE 8000 ENTRYPOINT ["flagsmith"] CMD ["migrate-and-serve"] HEALTHCHECK --interval=2s --timeout=2s --retries=3 --start-period=20s \ CMD flagsmith healthcheck tcp # * api-runtime-private [api-runtime] FROM api-runtime AS api-runtime-private # Install SAML binary dependency RUN apk add xmlsec # - Internal stages # * api-test [build-python] FROM build-python AS api-test COPY api /build/ RUN --mount=type=cache,target=/root/.cache/uv \ make install-packages opts='--extra dev' CMD ["make", "test"] # * api-private-test [build-python-private] FROM build-python-private AS api-private-test COPY api /build/ RUN --mount=type=secret,id=codeartifact_token \ --mount=type=cache,target=/root/.cache/uv \ UV_INDEX_FLAGSMITH_PYPI_PRODUCTION_USERNAME=aws \ UV_INDEX_FLAGSMITH_PYPI_PRODUCTION_PASSWORD="$(cat /run/secrets/codeartifact_token)" \ make install-packages opts='--extra dev --extra private' && \ make integrate-private-tests && \ git config --global --unset credential.helper && \ rm -f ${HOME}/.git-credentials CMD ["make", "test"] # - Target (shippable) stages # * private-cloud-api [api-runtime-private, build-python-private] FROM api-runtime-private AS private-cloud-api COPY --from=build-python-private /build/.venv/ /usr/local/ RUN python manage.py collectstatic --no-input RUN touch ./ENTERPRISE_VERSION USER nobody # * private-cloud-unified [api-runtime-private, build-python-private, build-node-django] FROM api-runtime-private AS private-cloud-unified COPY --from=build-python-private /build/.venv/ /usr/local/ COPY --from=build-node-django /build/api/ /app/ RUN python manage.py collectstatic --no-input RUN touch ./ENTERPRISE_VERSION USER nobody # * saas-api [api-runtime-private, build-python-private] FROM api-runtime-private AS saas-api # Install GnuPG and import private key RUN --mount=type=secret,id=sse_pgp_pkey \ apk add gpg gpg-agent && \ gpg --import /run/secrets/sse_pgp_pkey && \ mv /root/.gnupg/ /app/ && \ chown -R nobody /app/.gnupg/ COPY --from=build-python-private /build/.venv/ /usr/local/ RUN python manage.py collectstatic --no-input RUN touch ./SAAS_DEPLOYMENT USER nobody # * oss-api [api-runtime, build-python] FROM api-runtime AS oss-api COPY --from=build-python /build/.venv/ /usr/local/ RUN python manage.py collectstatic --no-input USER nobody # * oss-frontend [build-node-selfhosted] FROM wolfi-base AS oss-frontend ARG NODE_VERSION RUN apk add nodejs-${NODE_VERSION} WORKDIR /srv/bt COPY --from=build-node-selfhosted /build/frontend/ /srv/bt/ ENV NODE_ENV=production ARG CI_COMMIT_SHA RUN echo ${CI_COMMIT_SHA} > /srv/bt/CI_COMMIT_SHA COPY .release-please-manifest.json /srv/bt/.versions.json EXPOSE 8080 CMD ["node", "./api/index.js"] USER nobody # * oss-unified [api-runtime, build-python, build-node-django] FROM api-runtime AS oss-unified COPY --from=build-python /build/.venv/ /usr/local/ COPY --from=build-node-django /build/api/ /app/ RUN python manage.py collectstatic --no-input USER nobody