<@ULVA73B9P> What are the different ways of specif...
# ask-marvin
j
@Marvin What are the different ways of specifying my python dependencies for a flow that is deployed with prefect.yaml and will be pulled from bitbucket by a worker?
m
thought for 544.6 seconds
Great question. Assuming Prefect 3.x and a deployment defined by prefect.yaml with code pulled from Bitbucket at runtime, you have a few solid ways to make sure your Python dependencies are available. The best choice depends on your worker type and how reproducible you want runs to be. High-level options - Bake dependencies into a container image (recommended for Docker/Kubernetes workers) - Most reproducible and fastest: build a Docker image that already has your deps installed and reference it in your work pool or per-deployment job variables. - Install dependencies at runtime via prefect.yaml pull steps - After cloning from Bitbucket, run a step to install from requirements.txt or pyproject. Slower, but simple if you can’t build/publish images. - Pre-install dependencies on the host (Process worker) - If you use a process worker, install everything on the worker machine/venv ahead of time and run the worker inside that environment. Details and examples 1) Bake deps into a Docker image (Docker/Kubernetes workers) - Build an image (manually in CI or via your own Docker tooling) using your requirements.txt or pyproject, then reference the image in your work pool or deployment. - Pros: reproducible, cached, fast cold starts. Cons: requires image build/push. Example Dockerfile
Copy code
FROM prefecthq/prefect:3-latest
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Or, if you use a pyproject:
# COPY pyproject.toml poetry.lock ./
# RUN pip install -U pip && pip install .
Reference this image from prefect.yaml
Copy code
deployments:
- name: etl-prod
  entrypoint: flows/etl.py:etl
  work_pool:
    name: my-docker-pool
  job_variables:
    image: "my-registry/my-etl:1.2.3"
  pull:
  - prefect.deployments.steps.pull.git_clone:
      repository: <https://bitbucket.org/org/repo.git>
      branch: main
      access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
You can set the image at the work pool level (base job template) or per deployment via
job_variables.image
. Docs: - Docker worker how-to: Docker worker - Kubernetes worker how-to: Kubernetes worker - Work pools and job variables: Customize job variables 2) Install deps at runtime in pull steps (works with all workers) - Use prefect.yaml pull steps to clone your Bitbucket repo, then install packages before the flow runs. - Pros: no image build required. Cons: slower startup; requires network access and consistent package resolution at runtime. Example with requirements.txt
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.pip_install_requirements:
    directory: "{{ clone.directory }}"
    requirements_file: "requirements.txt"
Example with pyproject/poetry/uv
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    script: |
      python -m pip install -U pip
      # For pyproject-only:
      pip install -e "{{ clone.directory }}"
      # Or using uv:
      # pip install uv
      # uv pip install -r "{{ clone.directory }}/requirements.txt"
Docs: - prefect.yaml reference: prefect.yaml - Pull steps API: pull steps - Utility steps (pip install / run shell): utility steps
3) Pre-install on the host (Process worker) - Create and activate a virtual environment on the worker host and install your dependencies there. Start the worker from that environment, then let the worker pull and run your flow code. Example
Copy code
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
prefect worker start --pool my-process-pool
- Pros: simple, no image registry. Cons: less isolation, harder to manage across versions/environments. Docs: - Workers overview: Workers concept Bitbucket specifics - For private repos, provide credentials or a token to the
git_clone
step (via
access_token
or a Credentials block). See Prefect’s Bitbucket integration: Prefect Bitbucket integration What not to use in Prefect 3 - There is no
project.dependencies
field in prefect.yaml. - Avoid 2.x-era commands like
prefect deployment build
or `Deployment.build_from_flow()`; in 3.x use
prefect deploy
and/or
flow.from_source(...).deploy(...)
. Recommendations - Docker/Kubernetes worker: bake dependencies into a versioned image and reference it via job_variables.image or the pool’s base job template. Use runtime installs only when you can’t build images. - Process worker: install on the host or add a runtime install step as shown above (be mindful of performance and reproducibility). - Pin to a branch or specific commit_sha in
git_clone
for consistent results. If you can share which worker type you’re using (process, docker, or kubernetes) and whether you prefer pip/poetry/uv, I can give you a tailored prefect.yaml snippet that matches your setup.
j
@Marvin What if I have a "uv.lock" instead of "requirements.txt"?
m
thought for 50.2 seconds
Short answer: you can absolutely use uv. The main approaches are: - Recommended for Docker/Kubernetes workers: build an image that installs deps from pyproject.toml + uv.lock via uv at build time, then point your deployment at that image. - If you can’t build images: in prefect.yaml pull steps, use uv to export a requirements list from uv.lock, then use Prefect’s pip installer step. - For process workers: run uv on the host ahead of time and start the worker from that environment. Details and examples 1) Bake deps into your image with uv (best for Docker/Kubernetes) - Build your image in CI using uv to lock/sync, then make that the image for the work pool or the deployment’s job variables.
Copy code
# Dockerfile
FROM prefecthq/prefect:3-latest
WORKDIR /app

# Bring in project metadata first to leverage Docker layer caching
COPY pyproject.toml uv.lock ./

# Install uv and sync deps into a project venv
RUN python -m pip install -U pip uv \
 && uv sync --frozen --no-dev \
 && echo 'export PATH="/app/.venv/bin:$PATH"' >> /etc/profile

# Ensure the venv’s bin is first on PATH for all commands
ENV PATH="/app/.venv/bin:${PATH}"

# Copy the rest of your code
COPY . .
prefect.yaml (point deployment at the image)
Copy code
deployments:
- name: etl-prod
  entrypoint: flows/etl.py:etl
  work_pool:
    name: my-docker-pool
  job_variables:
    image: "my-registry/my-etl:1.2.3"
  pull:
  - prefect.deployments.steps.pull.git_clone:
      repository: <https://bitbucket.org/org/repo.git>
      branch: main
      access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
Docs: - Docker worker: Docker worker - Kubernetes worker: Kubernetes worker - Job variables: Customize job variables 2) Use uv in pull steps (no image build) - Export a requirements list from uv.lock, then use Prefect’s pip install step. This installs into the interpreter environment the worker will use to start the flow run.
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    script: |
      python -m pip install -U pip uv
      cd "{{ clone.directory }}"
      # Export a locked requirements list from pyproject + uv.lock
      uv export --frozen --no-dev > requirements.txt
- prefect.deployments.steps.utility.pip_install_requirements:
    directory: "{{ clone.directory }}"
    requirements_file: "requirements.txt"
Notes: - Avoid creating a brand-new venv in a pull step unless you also ensure the flow run process uses it (e.g., by baking PATH changes into an image). The flow run won’t automatically “activate” a venv created during pull steps. Docs: - prefect.yaml: prefect.yaml - Pull steps: pull steps - Utility steps: utility steps 3) Process worker: install with uv on the host - Use uv to sync in your project directory, then start the worker from that environment.
Copy code
# On the worker host:
python -m pip install -U pip uv
uv sync --frozen --no-dev     # creates/uses .venv by default
source .venv/bin/activate
prefect worker start --pool my-process-pool
If you tell me which worker you’re using (process, docker, or kubernetes), I can tailor the exact snippet for your setup.
j
@Marvin Process worker.
m
thought for 97.5 seconds
Perfect — for a Process worker, your flow runs in the same Python environment as the worker process. That means your dependencies must be installed into that environment. With a uv.lock, you’ve got two good paths: Recommended: pre-provision the worker’s environment with uv, then start the worker - Create a dedicated venv on the worker host, sync from pyproject.toml + uv.lock, and run the worker from that venv. This gives reproducible, fast starts and avoids mutating the environment at run time. Example bootstrap on the worker host:
Copy code
python -m pip install -U pip uv
git clone <https://bitbucket.org/org/repo.git> /opt/my-project   # or copy pyproject.toml + uv.lock there
cd /opt/my-project

# Create and sync a project venv from uv.lock
uv sync --frozen --no-dev
source .venv/bin/activate

# Start your process worker in this environment
prefect worker start --pool my-process-pool
Notes: - When your code changes or the lock file updates, re-run
uv sync --frozen
in that environment, then restart the worker. - If you run multiple deployments with different lock files, use separate workers/pools and venvs for isolation. Alternative: install deps at runtime via prefect.yaml pull steps (slower, but no pre-provisioning) - Let the worker clone the repo at run time and install into its current environment from uv.lock before executing the flow. Example prefect.yaml pull steps:
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    # Export a pinned requirements list from uv.lock
    script: |
      python -m pip install -U pip uv
      cd "{{ clone.directory }}"
      uv export --frozen --no-dev > requirements.txt
- prefect.deployments.steps.utility.pip_install_requirements:
    directory: "{{ clone.directory }}"
    requirements_file: "requirements.txt"
Or, using uv to sync the current interpreter directly (no extra step):
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    script: |
      python -m pip install -U uv
      cd "{{ clone.directory }}"
      uv export --frozen --no-dev > req.txt
      uv pip sync req.txt
Important considerations for process workers - Runtime installs mutate the worker’s environment. If multiple runs overlap, they can interfere with each other. If you must install at runtime, prefer a dedicated worker/pool and consider a pool concurrency limit of 1. - Avoid creating a brand-new venv during pull steps; the flow run won’t automatically “activate” it. Install into the worker’s existing interpreter instead. - For best reliability and performance, pre-provision the venv and start the worker inside it. Relevant docs - Workers concept: Workers - prefect.yaml: prefect.yaml - Pull steps: pull steps - Utility steps: utility steps If you share how many different projects/deployments this worker will handle, I can recommend whether to pre-provision one superset environment or split into multiple workers.
j
@Marvin I am getting an error "No module named pip" How can I fix it?
m
thought for 104.5 seconds
That error means the Python interpreter your Process worker is using doesn’t have pip available. You’ve got two ways to fix it: Option A — Install pip into the worker’s Python (keep your current prefect.yaml) - Try ensurepip first:
Copy code
python -m ensurepip --upgrade
python -m pip --version
- If ensurepip isn’t available, use your OS package manager: - Debian/Ubuntu:
Copy code
sudo apt-get update
    sudo apt-get install -y python3-pip python3-venv
- RHEL/CentOS/Amazon Linux:
Copy code
sudo yum install -y python3-pip
- Alpine:
Copy code
sudo apk add --no-cache py3-pip
- If you can’t use the package manager, bootstrap pip:
Copy code
curl -sSLO <https://bootstrap.pypa.io/get-pip.py>
python get-pip.py
- Verify the worker’s Python sees pip:
Copy code
python -c "import sys; print(sys.executable)"
python -m pip --version
If your prefect.yaml uses the
pip_install_requirements
step, it will work once pip is present: - Docs: utility steps - Pull steps: pull steps Option B — Avoid pip entirely; use uv in the pull steps (recommended if you already use uv.lock) Replace your pip install step with uv commands that install into the current interpreter (no venv activation needed):
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    script: |
      # Install uv without pip
      curl -LsSf <https://astral.sh/uv/install.sh> | sh
      export PATH="$HOME/.local/bin:$PATH"

      cd "{{ clone.directory }}"
      # Export a resolved requirements list from uv.lock
      uv export --frozen --no-dev > req.txt
      # Sync the CURRENT interpreter (the one starting your flow run)
      uv pip sync req.txt
Notes for Process workers - If you install at runtime, you’re mutating the worker’s environment. If you might have overlapping runs, set the pool concurrency to 1 or dedicate a worker to this deployment to avoid race conditions. - Best practice is to pre-provision the worker’s venv with uv and start the worker from that venv:
Copy code
python -m pip install -U pip uv || python -m ensurepip --upgrade && python -m pip install -U uv
cd /path/to/your/project
uv sync --frozen --no-dev
source .venv/bin/activate
prefect worker start --pool my-process-pool
If you tell me your OS and how Python was installed, I can give the exact commands to get pip (or uv) set up cleanly.
j
@Marvin I can't modify $HOME. Is there a prefect variable for the current bin directory that I can reference during the pull to point to as the target for installing uv?
m
thought for 112.8 seconds
Short answer: there isn’t a built-in Prefect variable for “the current bin directory.” However, you can reliably derive it at runtime from the Python interpreter the Process worker is using and install uv there (no $HOME needed). Here’s a pull-step pattern that: - Finds the interpreter’s bin dir - Falls back to a project-local bin dir if the interpreter’s bin isn’t writable - Installs uv into that directory - Uses uv with an absolute path to export and sync deps from uv.lock
Copy code
pull:
- prefect.deployments.steps.pull.git_clone:
    id: clone
    repository: <https://bitbucket.org/org/repo.git>
    branch: main
    access_token: "{{ prefect.blocks.secret.bitbucket_token }}"
- prefect.deployments.steps.utility.run_shell_script:
    script: |
      # Determine a bin dir we can write to
      BIN_DIR="$(python - <<'PY'
import os, sys
print(os.path.dirname(sys.executable))
PY
      )"

      if [ ! -w "$BIN_DIR" ]; then
        BIN_DIR="{{ clone.directory }}/.prefect_bin"
        mkdir -p "$BIN_DIR"
      fi

      # Install uv into BIN_DIR without touching $HOME
      curl -LsSf https://astral.sh/uv/install.sh -o /tmp/install-uv.sh
      sh /tmp/install-uv.sh --bin-dir "$BIN_DIR" || (export UV_INSTALL_DIR="$BIN_DIR"; sh /tmp/install-uv.sh)

      "$BIN_DIR/uv" --version

      # Use uv to export and sync into the CURRENT interpreter
      cd "{{ clone.directory }}"
      "$BIN_DIR/uv" export --frozen --no-dev > req.txt
      "$BIN_DIR/uv" pip sync req.txt
Notes - For a Process worker, this mutates the worker’s environment. If runs might overlap, consider dedicating a worker/pool and/or setting the pool concurrency to 1. - Best practice remains: pre-provision a venv for the worker and start the worker from that venv; then the computed BIN_DIR will be the venv’s bin and writable. - Environment changes from one pull step don’t persist to the next, so use absolute paths (as shown) or keep everything in a single shell step. References - Pull steps: pull steps - Utility steps (run_shell_script): utility steps If you share whether your worker runs inside a venv or system Python, I can tailor the BIN_DIR logic further.