<@ULVA73B9P> Can you show me an example of a custo...
# ask-marvin
m
@Marvin Can you show me an example of a custom docker container I can use to run my flow with in a GCP Cloud Run worker pool?
m
thought for 159.8 seconds
Absolutely — here’s a solid, minimal Dockerfile pattern for a custom image to use with a Prefect 3.x Cloud Run worker pool. It starts from the official Prefect image and adds your dependencies. The worker will inject the command to run the flow, so don’t override the entrypoint. Dockerfile
Copy code
# 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)
Copy code
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.
m
@Marvin Is
uv pip
the only way for this work? Can I use
uv sync
and the venv it generates?
m
thought for 47.6 seconds
Short answer: no—`uv pip` is not required. You can use
uv 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
Copy code
# 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)
Copy code
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.
m
@Marvin if I do
ENV 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?
m
thought for 24.5 seconds
Short answer: you can prepend your venv to PATH, but then you should include Prefect in that venv. Otherwise you risk mixing environments. Details - If you do `ENV PATH="$VIRTUAL_ENV/bin:$PATH"`: - The venv’s
python
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.
Copy code
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.
Copy code
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.
m
@Marvin this is my dockerfile:
Copy code
FROM 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"
m
thought for 63.8 seconds
Good start! A couple of gotchas with your current Dockerfile: - You create a venv in the build stage with
uv 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).
Copy code
# 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.
Copy code
# 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.
- Make sure the Prefect base tag you’re using exists (e.g.,
3-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.
m
@Marvin Any ideas why the python environment can't find data_blocks workspace package?
Copy code
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 . .

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
Copy code
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'")
m
thought for 40.1 seconds
Likely causes: - The venv you created in the build stage isn’t present in the runtime image. You set `VIRTUAL_ENV`/`PATH` to
/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)
Copy code
# 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
Copy code
# 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:
Copy code
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):
Copy code
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:
- Where
data_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.
m
@Marvin I mis-copied my dockerfile and do have
COPY --from=build /opt/prefect/data-flows/.venv /.venv
m
thought for 27.8 seconds
Ah — the key issue is the venv path mismatch. You’re copying the venv to /.venv:
Copy code
COPY --from=build /opt/prefect/data-flows/.venv /.venv
…but you’re exporting:
Copy code
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
Copy code
ENV VIRTUAL_ENV=/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
- Or copy the venv into the working dir and keep your existing ENV
Copy code
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:
Copy code
uv sync --frozen --package data-flows --package data-blocks
(adjust the package names to match your pyproject) Sanity checks to run in the image:
Copy code
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.