# The version of Python in the final image ARG PYTHON_VERSION=3.11 # The base image to use for the final image; Prefect and its Python requirements will # be installed in this image. The default is the official Python slim image. # The following images are also available in this file: # prefect-conda: Derivative of continuum/miniconda3 with a 'prefect' environment. Used for the 'conda' flavor. # Any image tag can be used, but it must have apt and pip. ARG BASE_IMAGE=python:${PYTHON_VERSION}-slim # The version used to build the Python distributable. ARG BUILD_PYTHON_VERSION=3.11 # The version used to build the V1 UI distributable. ARG NODE_VERSION=22.23.4 # The version used to build the V2 UI distributable (requires Node 22+). ARG NODE_V2_VERSION=22.23.4 # SQLite version — must match the tag published to prefecthq/prefect-sqlite on DockerHub # See Dockerfile.sqlite-builder and .github/workflows/sqlite-builder.yaml ARG SQLITE_VERSION=3.50.4 # Any extra Python requirements to install ARG EXTRA_PIP_PACKAGES="" # Pull pre-compiled SQLite binaries (built by .github/workflows/sqlite-builder.yaml). # This avoids compiling SQLite from source on every build. # To publish a new version: bump SQLITE_VERSION and run the sqlite-builder workflow. FROM prefecthq/prefect-sqlite:${SQLITE_VERSION} AS sqlite-builder # Build the V1 UI distributable. FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-bookworm-slim AS ui-builder WORKDIR /opt/ui RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \ apt-get update && \ apt-get upgrade --no-install-recommends -y && \ apt-get install --no-install-recommends -y \ # Required for arm64 builds chromium \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install dependencies separately so they cache COPY ./ui/package*.json ./ RUN npm ci --legacy-peer-deps # Build static UI files COPY ./ui . RUN npm run build # Build the V2 UI distributable. FROM --platform=$BUILDPLATFORM node:${NODE_V2_VERSION}-bookworm-slim AS ui-v2-builder # Optional Amplitude API key for analytics (build still works without it) ARG VITE_AMPLITUDE_API_KEY="" ENV VITE_AMPLITUDE_API_KEY=$VITE_AMPLITUDE_API_KEY WORKDIR /opt/ui-v2 RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \ apt-get update && \ apt-get upgrade --no-install-recommends -y && \ apt-get install --no-install-recommends -y \ # Required for arm64 builds chromium \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install dependencies separately so they cache COPY ./ui-v2/package*.json ./ RUN npm ci --legacy-peer-deps # Build static UI files COPY ./ui-v2 . RUN npm run build # Build the Python distributable. # Without this build step, versioningit cannot infer the version without git FROM python:${BUILD_PYTHON_VERSION}-slim AS python-builder WORKDIR /opt/prefect RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \ apt-get update && \ apt-get upgrade --no-install-recommends -y && \ apt-get install --no-install-recommends -y \ gpg \ git=1:2.* \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && dpkg --compare-versions "$(dpkg-query -W -f='${Version}' git)" ge '1:2.47.3' || \ (echo "ERROR: git version must be >= 1:2.47.3" && exit 1) # Install UV from official image - pin to specific version for build caching COPY --from=ghcr.io/astral-sh/uv:0.13.4 /uv /bin/uv # Copy the repository in; requires full git history for versions to generate correctly COPY . ./ # Package the V1 UI into the distributable. COPY --from=ui-builder /opt/ui/dist ./src/prefect/server/ui # Package the V2 UI into the distributable. COPY --from=ui-v2-builder /opt/ui-v2/dist ./src/prefect/server/ui-v2 # Create a source distributable archive; ensuring existing dists are removed first RUN rm -rf dist && PREFECT_REQUIRE_PACKAGED_UI_BUNDLES=1 uv build --sdist --out-dir dist RUN mv "dist/prefect-"*".tar.gz" "dist/prefect.tar.gz" # Export the locked versions so the final image installs what CI tests against # (`uv sync --locked`) instead of whatever PyPI resolves to on build day. All # extras are exported so one file covers every PREFECT_EXTRAS flavor. RUN uv export --frozen --no-dev --all-extras --no-emit-project --no-hashes -o dist/constraints.txt # Setup a base final image from miniconda FROM continuumio/miniconda3:26.7.1 AS prefect-conda # Create a new conda environment with our required Python version ARG PYTHON_VERSION RUN conda create \ python=${PYTHON_VERSION} \ --name prefect # Use the prefect environment by default RUN echo "conda activate prefect" >> ~/.bashrc SHELL ["/bin/bash", "--login", "-c"] # Build the final image with Prefect installed and our entrypoint configured FROM ${BASE_IMAGE} AS final # Redeclare ARGs needed in this stage ARG BASE_IMAGE ARG PYTHON_VERSION ARG SQLITE_VERSION ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 ENV UV_LINK_MODE=copy ENV UV_SYSTEM_PYTHON=1 # Ensure Python uses the upgraded SQLite library ENV LD_LIBRARY_PATH=/usr/local/lib LABEL maintainer="help@prefect.io" \ io.prefect.python-version=${PYTHON_VERSION} \ io.prefect.sqlite-version=${SQLITE_VERSION} \ org.label-schema.schema-version="1.0" \ org.label-schema.name="prefect" \ org.label-schema.url="https://www.prefect.io/" WORKDIR /opt/prefect # Install system requirements # For Debian Bookworm (used by conda base), we need to add Trixie sources to get git >= 2.47.3 # This is because miniconda3 images are still based on Bookworm which only has git ~2.39 # We install tini and build-essential first (from the base distro), then handle git separately RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \ apt-get update && \ apt-get upgrade --no-install-recommends -y && \ apt-get install --no-install-recommends -y \ tini=0.19.* \ build-essential \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install git - for Bookworm, we need to use Trixie sources since git >= 2.47.3 is required # and Bookworm only provides ~2.39. We allow apt to pull git's dependencies from Trixie as needed. RUN DEBIAN_VERSION=$(. /etc/os-release && echo $VERSION_CODENAME) && \ if [ "$DEBIAN_VERSION" = "bookworm" ]; then \ echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list; \ apt-get update; \ apt-get install --no-install-recommends -y -t trixie git; \ rm -f /etc/apt/sources.list.d/trixie.list; \ else \ apt-get update; \ apt-get install --no-install-recommends -y git; \ fi \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && dpkg --compare-versions "$(dpkg-query -W -f='${Version}' git)" ge '1:2.47.3' || \ (echo "ERROR: git version must be >= 1:2.47.3" && exit 1) # Copy pre-compiled SQLite ${SQLITE_VERSION} from sqlite-builder stage COPY --from=sqlite-builder /usr/local/lib/libsqlite3* /usr/local/lib/ COPY --from=sqlite-builder /usr/local/include/sqlite3*.h /usr/local/include/ COPY --from=sqlite-builder /usr/local/bin/sqlite3 /usr/local/bin/ COPY --from=sqlite-builder /usr/local/lib/pkgconfig/sqlite3.pc /usr/local/lib/pkgconfig/ RUN ldconfig # Install UV from official image - pin to specific version for build caching COPY --from=ghcr.io/astral-sh/uv:0.13.4 /uv /bin/uv # Extras to include during installation ARG PREFECT_EXTRAS=[redis,client,otel] # Install prefect from the sdist. # # The sdist is bind-mounted rather than copied. A COPY commits the archive to its # own layer, and a `rm -rf` in a later RUN can only add a whiteout on top of that # layer -- it cannot remove it from the image, so the archive ships in everything # we push. A bind mount is never committed to a layer. RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,from=python-builder,source=/opt/prefect/dist,target=/dist \ UV_COMPILE_BYTECODE=1 uv pip install --constraint /dist/constraints.txt \ "/dist/prefect.tar.gz${PREFECT_EXTRAS:-""}" # Setuptools is required by pip in the conda environment. Remove it only from # base images where it is not managed as part of the environment. RUN if [ "${BASE_IMAGE}" != "prefect-conda" ]; then \ uv pip uninstall setuptools; \ fi # Install any extra packages ARG EXTRA_PIP_PACKAGES RUN --mount=type=cache,target=/root/.cache/uv \ [ -z "${EXTRA_PIP_PACKAGES:-""}" ] || UV_COMPILE_BYTECODE=1 uv pip install "${EXTRA_PIP_PACKAGES}" # Smoke test RUN prefect version # Setup entrypoint COPY scripts/entrypoint.sh ./entrypoint.sh ENTRYPOINT ["/usr/bin/tini", "-g", "--", "/opt/prefect/entrypoint.sh"]