Janet Carson
11/11/2025, 10:26 PMMarvin
11/11/2025, 10:26 PMMarvin
11/11/2025, 10:35 PMFROM 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
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
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
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 stepsMarvin
11/11/2025, 10:35 PMpython -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.Janet Carson
11/11/2025, 10:47 PMMarvin
11/11/2025, 10:47 PMMarvin
11/11/2025, 10:48 PM# 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)
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.
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.
# 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.Janet Carson
11/11/2025, 11:02 PMMarvin
11/11/2025, 11:02 PMMarvin
11/11/2025, 11:04 PMpython -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:
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):
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.Janet Carson
11/11/2025, 11:06 PMMarvin
11/11/2025, 11:06 PMMarvin
11/11/2025, 11:08 PMpython -m ensurepip --upgrade
python -m pip --version
- If ensurepip isn’t available, use your OS package manager:
- Debian/Ubuntu:
sudo apt-get update
sudo apt-get install -y python3-pip python3-venv
- RHEL/CentOS/Amazon Linux:
sudo yum install -y python3-pip
- Alpine:
sudo apk add --no-cache py3-pip
- If you can’t use the package manager, bootstrap pip:
curl -sSLO <https://bootstrap.pypa.io/get-pip.py>
python get-pip.py
- Verify the worker’s Python sees pip:
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):
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:
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.Janet Carson
11/11/2025, 11:10 PMMarvin
11/11/2025, 11:10 PMMarvin
11/11/2025, 11:12 PMpull:
- 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.