Mike Dedys
12/10/2025, 12:17 PMMarvin
12/10/2025, 12:17 PMMarvin
12/10/2025, 12:20 PM# syntax=docker/dockerfile:1.7
# Use the official Prefect 3 base image
ARG PREFECT_VERSION=3-latest
FROM prefecthq/prefect:${PREFECT_VERSION}
# Optional: install OS packages your tasks need
RUN apt-get update && apt-get install --no-install-recommends -y \
git curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Option A: pin your Python deps with a requirements file
# (keeps builds fast with a cache for uv)
COPY requirements.txt /tmp/requirements.txt
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install -r /tmp/requirements.txt
# Option B: or install inline (uncomment and edit)
# RUN --mount=type=cache,target=/root/.cache/uv \
# uv pip install \
# "prefect-gcp" \
# "pandas==2.2.2"
# IMPORTANT:
# - Do NOT change ENTRYPOINT or CMD.
# The Cloud Run worker injects the correct command:
# `prefect flow-run execute`
Notes
- You typically do NOT copy your flow code into this image when using Cloud Run workers. Prefect will fetch your flow code from your deployment’s source at runtime (e.g., Git via flow.from_source(...).deploy(...)).
- If you need GCP services (e.g., Cloud SQL), install the right extras (e.g., prefect-gcp[cloud-sql]) in the image.
How to use this image in your deployment (prefect.yaml excerpt)
deployments:
- name: my-deployment
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: my-cloud-run-pool
job_variables:
image: us-docker.pkg.dev/my-project/prefect/my-image:latest
region: us-central1
cpu: "1000m"
memory: "1Gi"
env:
MY_SETTING: "value"
GCP/Cloud Run specifics to remember
- The worker will set the command for you (prefect flow-run execute) and inject required Prefect env vars.
- Use a GCP service account with permissions to run Cloud Run Jobs (e.g., roles/run.admin) and to act as that service account (roles/iam.serviceAccountUser).
- Start with cpu: "1000m" and memory: "512Mi"–"1Gi" and adjust as needed.
Docs
- GCP Worker Guide: https://docs.prefect.io/integrations/prefect-gcp/gcp-worker-guide
- Prefect GCP integration: https://docs.prefect.io/integrations/prefect-gcp
- Workers overview: https://docs.prefect.io/how-to-guides/deploy-code/workers
If you share any special libraries or system dependencies your flow needs, I can tailor the Dockerfile further.Mike Dedys
12/10/2025, 12:20 PMuv pip the only way for this work? Can I use uv sync and the venv it generates?Marvin
12/10/2025, 12:21 PMMarvin
12/10/2025, 12:21 PMuv sync (or plain pip) in your Dockerfile. If you use uv sync, make sure the venv is on PATH and that it includes prefect so the worker-injected prefect flow-run execute command is found.
Option A: Use uv sync with a project venv
# syntax=docker/dockerfile:1.7
FROM prefecthq/prefect:3-latest
# Use a stable working directory
WORKDIR /app
# Copy only the lockfiles first for better layer caching
COPY pyproject.toml uv.lock ./
# Create a project venv with your exact deps (including prefect)
# Tip: --frozen ensures uv.lock is honored
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
# Make the venv the default for all subsequent commands
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# (Optional) verify
RUN python -c "import prefect, sys; print('Using:', sys.executable, 'Prefect:', prefect.__version__)"
# IMPORTANT: do not change ENTRYPOINT or CMD; the worker injects: `prefect flow-run execute`
Notes:
- Ensure prefect>=3,<4 is listed in your pyproject.toml dependencies so prefect is installed into the venv and available on PATH.
- This venv will be used at runtime for both the prefect CLI and your flow imports.
Option B: Stick with system Python and install deps globally (uv pip or pip)
FROM prefecthq/prefect:3-latest
# requirements.txt should NOT include prefect (already in base image)
COPY requirements.txt /tmp/requirements.txt
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install -r /tmp/requirements.txt
# Or:
# RUN pip install -r /tmp/requirements.txt
# Keep the base ENTRYPOINT; the worker injects the command for you
Notes:
- This avoids having two copies of Prefect (base image + venv). It’s simplest if you don’t need a per-project venv.
Key constraints for Cloud Run workers
- The worker sets the command to prefect flow-run execute. That binary must be on PATH.
- If you use a venv, export PATH to point to it and install prefect into that venv.
- Do not override the base image ENTRYPOINT.
Docs
- GCP Worker Guide: https://docs.prefect.io/integrations/prefect-gcp/gcp-worker-guide
- Prefect GCP integration: https://docs.prefect.io/integrations/prefect-gcp
- Workers overview: https://docs.prefect.io/how-to-guides/deploy-code/workers
If you share your dependency style (requirements.txt vs pyproject/uv.lock) I can tailor the Dockerfile exactly.Mike Dedys
12/10/2025, 1:32 PMENV PATH="$VIRTUAL_ENV/bin:$PATH" on the base image will it still leverage prefect from the base image or do i need to include it in my project.toml?Marvin
12/10/2025, 1:32 PMMarvin
12/10/2025, 1:32 PMpython will be used.
- If the venv does NOT contain prefect, the CLI will likely come from the base image later on PATH.
- That creates a mismatch: the prefect CLI (system Python) runs your flow while your dependencies are installed in the venv Python. Imports can fail.
Recommended patterns
- Option A (no venv): Don’t change PATH. Install your extra deps globally and use the base image’s Prefect.
FROM prefecthq/prefect:3-latest
COPY requirements.txt /tmp/requirements.txt
RUN --mount=type=cache,target=/root/.cache/uv uv pip install -r /tmp/requirements.txt
# Keep the base ENTRYPOINT; worker injects `prefect flow-run execute`
- Option B (venv): If you set PATH to your venv, install Prefect into that venv (include it in pyproject/uv.lock or install explicitly) so CLI and runtime match.
FROM prefecthq/prefect:3-latest
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Ensure Prefect is in the venv (via pyproject deps: prefect>=3,<4)
RUN which prefect && python -c "import prefect, sys; print('OK:', prefect.__version__, sys.executable)"
# Worker injects `prefect flow-run execute`
Rule of thumb
- If PATH points to your venv, include Prefect in that venv (pyproject.toml) so both prefect and your flow run under the same interpreter.
- If you want to “leverage the base image’s Prefect,” don’t prepend a venv to PATH—install your deps globally instead.Mike Dedys
12/10/2025, 2:15 PMFROM ghcr.io/astral-sh/uv:python3.13-bookworm AS build
WORKDIR /opt/prefect/data-flows
COPY . .
RUN uv sync --no-install-workspace --package data-flows
RUN uv sync --package data-flows
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
COPY --from=build /opt/prefect/data-flows/data/config/ ./data/config/
COPY --from=build /opt/prefect/data-flows/data/blocks/ ./data/blocks/
COPY --from=build /opt/prefect/data-flows/data/flows/ ./data/flows/
COPY --from=build /opt/prefect/data-flows/pyproject.toml ./pyproject.toml
COPY --from=build /opt/prefect/data-flows/uv.lock ./uv.lock
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"Marvin
12/10/2025, 2:15 PMMarvin
12/10/2025, 2:16 PMuv sync, but you don’t copy that venv into the runtime image.
- You set VIRTUAL_ENV=/app/.venv while your WORKDIR is /opt/prefect/data-flows — that path won’t exist.
- If you prepend the venv to PATH but it doesn’t contain Prefect, the prefect CLI used by the Cloud Run worker won’t be found (or you’ll mix interpreters).
You’ve got two clean paths. Pick one:
Option 1 — Keep and use a venv
- Copy the venv from the build stage.
- Point VIRTUAL_ENV to the right path.
- Ensure Prefect is installed in that venv (include prefect>=3,<4 in your pyproject).
# syntax=docker/dockerfile:1.7
FROM <http://ghcr.io/astral-sh/uv:python3.13-bookworm|ghcr.io/astral-sh/uv:python3.13-bookworm> AS build
WORKDIR /opt/prefect/data-flows
# Copy project files first for caching (add your source files as needed)
COPY pyproject.toml uv.lock ./
# Copy the rest of your project if uv needs local paths
COPY . .
# Create project venv with exact deps (honors uv.lock)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
# Copy only what you need at runtime
COPY --from=build /opt/prefect/data-flows/data/config/ ./data/config/
COPY --from=build /opt/prefect/data-flows/data/blocks/ ./data/blocks/
COPY --from=build /opt/prefect/data-flows/data/flows/ ./data/flows/
COPY --from=build /opt/prefect/data-flows/pyproject.toml ./pyproject.toml
COPY --from=build /opt/prefect/data-flows/uv.lock ./uv.lock
# Copy the venv created by uv sync
COPY --from=build /opt/prefect/data-flows/.venv /opt/prefect/data-flows/.venv
# Activate the venv for the runtime
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Optional: sanity check – should show python from .venv and prefect available
RUN which python && python -V && which prefect && prefect version
# Keep base ENTRYPOINT; Cloud Run worker injects: `prefect flow-run execute`
Notes:
- uv sync creates .venv in the project dir by default; copying it ensures your runtime uses the same environment you built.
- Because PATH is pointed at the venv, Prefect must be in your pyproject dependencies so prefect is available to the worker-injected command.
Option 2 — Don’t use a venv; install into the base image’s Python
- Simpler and avoids duplicating Prefect.
- Remove `VIRTUAL_ENV`/`PATH` lines.
- Use uv sync --no-venv (or uv pip install --system) in the runtime stage.
# syntax=docker/dockerfile:1.7
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
# Copy project metadata needed for dependency resolution
COPY pyproject.toml uv.lock ./
# Copy runtime files your flows import/use
COPY data/config/ ./data/config/
COPY data/blocks/ ./data/blocks/
COPY data/flows/ ./data/flows/
# Install your project deps into the system environment (no venv)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-venv
# Alternatively:
# RUN --mount=type=cache,target=/root/.cache/uv \
# uv pip install --system .
# Optional: sanity check – should show system python and prefect available
RUN which python && python -V && which prefect && prefect version
# Keep base ENTRYPOINT; Cloud Run worker injects: `prefect flow-run execute`
Which should you choose?
- Use Option 2 (no venv) if you’re happy to layer your deps on top of the Prefect base image — it’s simplest and avoids two Prefect installs.
- Use Option 1 (venv) if you need strict isolation or want to mirror your local uv sync environment exactly. Just be sure Prefect is included in the venv and that PATH points to that venv.
Cloud Run/Prefect reminders
- The worker sets the command to prefect flow-run execute. Whatever is first on PATH will be used to locate prefect.
- Don’t override the base ENTRYPOINT from the Prefect image.Marvin
12/10/2025, 2:16 PM3-latest, or 3-python3.13) and matches your Python version.
Docs
- GCP Worker Guide: https://docs.prefect.io/integrations/prefect-gcp/gcp-worker-guide
- Workers overview: https://docs.prefect.io/how-to-guides/deploy-code/workers
If you share your pyproject’s dependency style (and whether you want Prefect in the venv or to rely on the base image), I can tailor the Dockerfile exactly.Mike Dedys
12/10/2025, 2:30 PMFROM <http://ghcr.io/astral-sh/uv:python3.13-bookworm|ghcr.io/astral-sh/uv:python3.13-bookworm> AS build
WORKDIR /opt/prefect/data-flows
COPY . .
RUN uv sync --no-install-workspace --package data-flows
RUN uv sync --package data-flows
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
COPY --from=build /opt/prefect/data-flows/data/config/ ./data/config/
COPY --from=build /opt/prefect/data-flows/data/blocks/ ./data/blocks/
COPY --from=build /opt/prefect/data-flows/data/flows/ ./data/flows/
COPY --from=build /opt/prefect/data-flows/pyproject.toml ./pyproject.toml
COPY --from=build /opt/prefect/data-flows/uv.lock ./uv.lock
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
ENV PYTHONPATH=/opt/prefect/data-flows
File "/usr/local/lib/python3.13/site-packages/prefect/utilities/importtools.py", line 118, in load_script_as_module
raise ScriptError(user_exc=exc, path=path) from exc
prefect.exceptions.ScriptError: Script at 'data/flows/data_flows/etl/loopio/flows.py' encountered an exception: ModuleNotFoundError("No module named 'data_blocks'")Marvin
12/10/2025, 2:31 PMMarvin
12/10/2025, 2:31 PM/opt/prefect/data-flows/.venv but never copy that venv over or create one in the runtime stage.
- uv sync --package data-flows will only install the data-flows package and its declared dependencies. If data_blocks is a separate workspace package and not declared as a dependency of data-flows, it won’t be installed.
- If data_blocks lives at data/blocks/data_blocks/, PYTHONPATH=/opt/prefect/data-flows will not find it; you’d need /opt/prefect/data-flows/data/blocks on PYTHONPATH (brittle compared to installing the package).
Pick one of these fixes:
Option 1 — Use the venv from the build stage (ensure Prefect is in it)
# syntax=docker/dockerfile:1.7
FROM <http://ghcr.io/astral-sh/uv:python3.13-bookworm|ghcr.io/astral-sh/uv:python3.13-bookworm> AS build
WORKDIR /opt/prefect/data-flows
# Copy everything the resolver needs
COPY pyproject.toml uv.lock ./
COPY . .
# Create the project venv (honors uv.lock)
# Make sure data_flows depends on data_blocks in pyproject OR install both:
# uv sync --frozen --package data-flows --package data-blocks
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
# Copy runtime files (flows/config/etc.)
COPY --from=build /opt/prefect/data-flows/data/config/ ./data/config/
COPY --from=build /opt/prefect/data-flows/data/blocks/ ./data/blocks/
COPY --from=build /opt/prefect/data-flows/data/flows/ ./data/flows/
COPY --from=build /opt/prefect/data-flows/pyproject.toml ./pyproject.toml
COPY --from=build /opt/prefect/data-flows/uv.lock ./uv.lock
# Copy the venv produced by uv (this is the key bit missing now)
COPY --from=build /opt/prefect/data-flows/.venv /opt/prefect/data-flows/.venv
# Activate the venv
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Sanity check (should succeed)
RUN python -c "import prefect, data_blocks; print('OK', prefect.__version__)"
Notes:
- Because PATH points at the venv, Prefect must be in your pyproject deps so the prefect CLI is available for the worker-injected prefect flow-run execute.
Option 2 — Don’t use a venv; install into the base image’s Python
# syntax=docker/dockerfile:1.7
FROM prefecthq/prefect:3-python3.13 AS runtime
WORKDIR /opt/prefect/data-flows
COPY pyproject.toml uv.lock ./
COPY data/config/ ./data/config/
COPY data/blocks/ ./data/blocks/
COPY data/flows/ ./data/flows/
# Install project deps into the system environment
# Ensure data_flows depends on data_blocks in pyproject,
# or specify both packages when syncing
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-venv
# Alternative if the project is installable:
# RUN --mount=type=cache,target=/root/.cache/uv uv pip install --system .
# Sanity check
RUN python -c "import prefect, data_blocks; print('OK', prefect.__version__)"
Notes:
- Remove `VIRTUAL_ENV`/`PATH` lines. This keeps the base image’s Prefect and avoids duplicating it in a venv.
- For workspaces, make sure your pyproject.toml declares data_blocks as a dependency of data_flows, or sync both packages:
- uv sync --frozen --no-venv --package data-flows --package data-blocks
- Or remove --package and sync the workspace if that’s your intent.
If you really intend to rely on PYTHONPATH instead of installing packages, set it to the directory that actually contains the data_blocks package:
ENV PYTHONPATH=/opt/prefect/data-flows/data/blocks:$PYTHONPATH
…but installing the package (Option 1 or 2) is more robust.
Quick debugging inside the image (helps confirm the path problem):
RUN python - <<'PY'
import sys, pkgutil
print("exe:", sys.executable)
print("path:", sys.path)
print("prefect:", __import__("prefect").__version__)
print("data_blocks loader:", pkgutil.find_loader("data_blocks"))
PY
If you can share:Marvin
12/10/2025, 2:31 PMdata_blocks lives on disk
- Your pyproject.toml workspace/members and dependencies
…I can suggest the exact uv sync invocation and the minimal COPY set you need.Mike Dedys
12/10/2025, 2:32 PMCOPY --from=build /opt/prefect/data-flows/.venv /.venvMarvin
12/10/2025, 2:32 PMMarvin
12/10/2025, 2:32 PMCOPY --from=build /opt/prefect/data-flows/.venv /.venv
…but you’re exporting:
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
So your PATH points to /opt/prefect/data-flows/.venv/bin (which doesn’t exist), meaning the venv is not used at runtime and your imports come from the system environment where data_blocks isn’t installed.
Fix it one of two ways:
- Point ENV to the place you copied it
ENV VIRTUAL_ENV=/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
- Or copy the venv into the working dir and keep your existing ENV
COPY --from=build /opt/prefect/data-flows/.venv /opt/prefect/data-flows/.venv
ENV VIRTUAL_ENV=/opt/prefect/data-flows/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
Also double‑check these two things:
- Prefect in the venv: if PATH points to the venv, make sure your pyproject includes prefect>=3,<4 (otherwise prefect flow-run execute won’t be found or will use a different interpreter).
- data_blocks installed: if data_blocks is a separate workspace/local package, it must be installed into the venv. Either:
- declare it as a dependency of data-flows (path or workspace dep in pyproject), or
- install both explicitly during build:
uv sync --frozen --package data-flows --package data-blocks
(adjust the package names to match your pyproject)
Sanity checks to run in the image:
RUN which python && python -V
RUN which prefect && prefect version
RUN python -c "import sys, pkgutil; print('path:', sys.path); print('data_blocks:', pkgutil.find_loader('data_blocks'))"
If you can share the relevant pyproject sections (workspace/members and how data_blocks is referenced), I can suggest the exact uv sync invocation or dependency stanza.