Emmanuel Rieg
12/11/2025, 12:15 PMMarvin
12/11/2025, 12:15 PMMarvin
12/11/2025, 12:18 PMjob_variables, use the command key (it maps to the container args):
flow.deploy(
name="my-deployment",
work_pool_name="my-k8s-pool",
job_variables={
"command": "prefect flow-run execute"
}
)
Notes:
- Leave it unset unless you have a special wrapper/entrypoint; the default is correct for standard runs.
- Make sure your image has Prefect installed so the prefect CLI is available.
References:
- Default command in worker base: base.py
- Kubernetes worker variables (what job_variables you can set): prefect_kubernetes.worker.KubernetesWorkerVariables
- How to run flows on Kubernetes: Docs: KubernetesEmmanuel Rieg
12/11/2025, 12:23 PMMarvin
12/11/2025, 12:27 PMMarvin
12/11/2025, 12:28 PMget_run_logger().info(...)) are sent from the pod to the Prefect API and show up in the UI automatically.
- The stream_output job variable in the Kubernetes worker only mirrors pod stdout/stderr to the worker’s local terminal; it does not affect what the UI shows.
- Anything printed directly to stdout/stderr or produced by unrelated subprocesses isn’t automatically captured in the UI. To surface it, capture that output and send it via Prefect’s logger.
Good refs:
- Kubernetes worker variables (incl. stream_output): KubernetesWorkerVariables
- Prefect API log handler (how logs reach the UI): APILogHandler
If you want help adapting non-Prefect output to appear in the UI, share a snippet of how you’re running those commands and I’ll show you a pattern to capture and log them.Emmanuel Rieg
12/11/2025, 12:29 PMEmmanuel Rieg
12/11/2025, 12:32 PMMarvin
12/11/2025, 12:33 PMMarvin
12/11/2025, 12:37 PMflow.from_source(...).deploy(...) on Kubernetes.
A few quick questions to pinpoint the issue:
- What state do your flow runs show in the UI (Scheduled, Late, Pending, Running)?
- Do you have a Kubernetes worker running and polling the same work pool you deployed to?
- Which container image are you using in job_variables["image"]?
- Is your repo private? If so, how are you passing credentials to GitRepository(...)?
- Did you override job_variables["command"]? If yes, please remove it.
Most common causes and fixes:
1) No worker is picking up runs
- Ensure a worker is running and attached to the correct work pool.
- Check: prefect work-pool inspect "<your-pool>"
- Start a worker (ensure kubernetes integration is installed):
uv run --with 'prefect[kubernetes]' prefect worker start -p "<your-pool>"
Docs: Run flows on Kubernetes
2) Image doesn’t have Prefect (or git)
- The pod needs to run prefect flow-run execute and clone your repo at runtime. Your image must include:
- Prefect 3.x
- git CLI (required for GitRepository)
- Your Python dependencies
- If unsure, build a custom image, e.g.:
FROM prefecthq/prefect:3-python3.11
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# pip install your deps here
- Then set it in job_variables["image"].
3) Incorrectly overriding the container command
- Don’t set job_variables["command"] unless you have a very specific reason. The default is correct.
- If you set it to something else (like python my_script.py), Prefect won’t run the flow engine and you’ll see no Prefect logs.
- Remove the override to let the worker use the default command: prefect flow-run execute.
4) Private repo credentials not available at runtime
- If your repo is private, the runtime pod must be able to clone it. Prefer a Secret block for tokens:
from prefect.runner.storage import GitRepository
from prefect.blocks.system import Secret
repo = GitRepository(
url="<https://github.com/org/repo.git>",
credentials={"access_token": Secret.load("my-github-token")},
branch="main",
)
- Make sure you used a block (or a supported credentials dict) so the worker can resolve it at runtime.
- Guide: Store flow code from Git
5) Network/API connectivity from the pod
- The pod needs to reach both your Git host and your Prefect API (PREFECT_API_URL).
- If you’re self-hosting, confirm the cluster can reach your API URL and that PREFECT_API_KEY is set by the worker.
Quick verification checklist:
- In UI → Work Pools, confirm your pool shows an active worker.
- In UI → Flow Runs, check the state. If it’s stuck in Scheduled/Late, the worker isn’t submitting a job.
- Check worker logs for submission errors (image pull, RBAC, etc.).
- If a pod is created, check its logs/events:
kubectl -n <ns> get pods
kubectl -n <ns> describe pod <pod-name>
kubectl -n <ns> logs <pod-name>
If you can share:
- The deployment snippet (source/entrypoint, job_variables, image)
- The flow run state
- Whether the worker is running and any worker logs
…I can pinpoint the exact fix fast.
Helpful refs:
- Kubernetes how-to: Run flows on Kubernetes
- Deploy via Python: Deploy via Python
- Store flow code (GitRepository): Store flow code from GitEmmanuel Rieg
12/11/2025, 12:42 PMMarvin
12/11/2025, 12:42 PMMarvin
12/11/2025, 12:43 PMprefecthq/prefect:3.6.5-python3.11
- Using flow.from_source(GitRepository(...), entrypoint=...)
- Private repo, credentials provided
- No custom command
Most common root cause here: the container does not have git installed, so the runtime clone of your repo fails immediately and the pod exits before Prefect can emit logs to the API. The official Prefect images do not include git by default.
What to do:
1) Build an image with git (and your flow’s dependencies)
- Example Dockerfile:
FROM prefecthq/prefect:3.6.5-python3.11
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# Optional: pip install your Python deps here
# RUN pip install -r requirements.txt
- Push this image to your registry and update your deployment to use it:
flow.from_source(
source=GitRepository(...),
entrypoint="path/to/flow.py:my_flow",
).deploy(
name="my-deployment",
work_pool_name="my-k8s-pool",
job_variables={
"image": "your-registry/prefect-with-git:latest",
"namespace": "<your-ns>",
# optional: service_account_name, etc.
},
)
- Quick local check:
docker run --rm your-registry/prefect-with-git:latest git --version
2) Verify the pod’s failure reason
- This will confirm the diagnosis quickly:
kubectl -n <ns> describe pod <pod-name>
kubectl -n <ns> logs <pod-name>
If you see messages like “git: not found” or a clone/auth error, that’s the issue.
3) Double-check repo access at runtime
- For private repos, your credentials need to be resolvable in the pod:
from prefect.runner.storage import GitRepository
from prefect.blocks.system import Secret
repo = GitRepository(
url="<https://github.com/org/repo.git>",
credentials={"access_token": Secret.load("my-github-token")},
branch="main",
)
- Using a Secret block ensures credentials are injected safely at runtime.
4) Ensure the entrypoint path is correct
- Must be relative to the repo root:
"some/dir/flows.py:my_flow"
A wrong path/function name will crash after clone; pod logs will show a Python error.
5) Sanity checks
- Worker is running and attached to the same work pool
- Cluster can reach your Prefect API URL (if API is not reachable, logs won’t show in UI even if the engine starts; but you’ll still see the pod error in kubectl)
If you can paste:
- kubectl -n <ns> logs <pod-name> (first few lines)
- The entrypoint you’re using
- The job_variables snippet
…I can confirm if it’s purely the missing git package or something else.
Helpful refs:
- Run flows on Kubernetes: Docs: Kubernetes
- Store flow code from Git (requirements and behavior): Docs: Store flow code from Git
- Kubernetes worker variables: KubernetesWorkerVariablesEmmanuel Rieg
12/11/2025, 12:59 PM$ docker run -ti prefecthq/prefect:3.6.5-python3.11 git usage: git ...
entrypoint ="flows/b2r.py:b2r"
job_variables={
"namespace": "prefect",
"env": {
"PREFECT_LOGGING_LEVEL": "DEBUG",
"EXTRA_PIP_PACKAGES": "-r requirements.txt",
"ACCESS_KEY": "{{ prefect.blocks.secret.etl-bucket-access-key}}",
"SECRET_KEY": "{{ prefect.blocks.secret.etl-bucket-secret-key}}",
},
},
logs
$ kubectl logs -n prefect opal-mastiff-mdbts-88tzr
+uv pip install -r flows/requirements.txt
error: File not found: `flows/requirements.txt`
the requirements.ttxt is in the <repo>/flows/
@Marvin any idea ?Marvin
12/11/2025, 12:59 PMMarvin
12/11/2025, 1:03 PMEXTRA_PIP_PACKAGES running before your repo is cloned.
In Prefect’s images, the container entrypoint runs uv pip install $EXTRA_PIP_PACKAGES immediately on startup, then later the worker/runner pulls your Git repo and runs the flow. Since your requirements file lives inside the repo (flows/requirements.txt), it doesn’t exist yet when the entrypoint tries to install it — hence “File not found.”
Refs:
- Entry script (shows the install happens first): entrypoint.sh
- Using Git as storage: Store flow code from Git
Ways to fix it:
- Best for prod: bake deps into the image
- Dockerfile
FROM prefecthq/prefect:3.6.5-python3.11
# If you want to use a file in the repo, copy it into the image at build time:
COPY flows/requirements.txt /opt/prefect/requirements.txt
RUN uv pip install --system -r /opt/prefect/requirements.txt
- Then set job_variables["image"] to your custom image and remove EXTRA_PIP_PACKAGES.
- If you want to keep `EXTRA_PIP_PACKAGES`: pass package names, not a file path
job_variables={
"env": {
"EXTRA_PIP_PACKAGES": "boto3 pandas s3fs", # examples
}
}
This works because it doesn’t rely on repo files.
- Or, still use a requirements file with EXTRA_PIP_PACKAGES, but copy it into the image and reference the absolute path you copied to:
# Dockerfile
COPY flows/requirements.txt /opt/prefect/requirements.txt
# job_variables env
EXTRA_PIP_PACKAGES="-r /opt/prefect/requirements.txt"
Once you make one of those changes, the container will install deps successfully, then the runner will clone your repo and load `entrypoint="flows/b2r.py:b2r"`; at that point Prefect logs should start appearing as the flow runs.
If you share which approach you prefer (custom image vs. package names vs. copying the file into the image), I can provide a minimal working example tailored to your setup.Emmanuel Rieg
12/11/2025, 1:09 PMMarvin
12/11/2025, 1:09 PMMarvin
12/11/2025, 1:12 PMprefect.yaml and executed in the runtime environment.
Your options without YAML:
- Bake deps into your image (recommended for K8s). Build a custom image that installs your repo’s requirements and set it in job_variables["image"].
- Use EXTRA_PIP_PACKAGES for simple cases to install by name (not from a file inside the repo) at container startup.
- Manually invoke the step function in Python, but note it runs in your build environment, not in the worker’s runtime:
import asyncio
from prefect.deployments.steps.utility import pip_install_requirements
asyncio.run(
pip_install_requirements(
directory="/path/to/checked-out/repo",
requirements_file="requirements.txt",
stream_output=True
)
)
Or the generic runner:
from prefect.deployments.steps.core import run_step
asyncio.run(run_step({
"prefect.deployments.steps.pip_install_requirements": {
"directory": "/path/to/repo",
"requirements_file": "requirements.txt"
}
}))
This is useful in CI to pre-bake an image or prepare an artifact, but it won’t execute on the Kubernetes pod at flow-run time unless you put it in prefect.yaml pull steps.
If your goal is “install requirements from the repo at runtime in K8s”:
- Use prefect.yaml pull steps with pip_install_requirements so the pod installs after the repo is cloned, or
- Avoid runtime installs and bake deps into the image.
References:
- Pull steps API: run_step / run_steps
- pip_install_requirements: pip_install_requirements
- Deploy via Python: Deploy via Python
- Store flow code (from_source): Store flow code from GitEmmanuel Rieg
12/11/2025, 1:14 PMMarvin
12/11/2025, 1:14 PMMarvin
12/11/2025, 1:15 PMEXTRA_PIP_PACKAGES is just passed to the container entrypoint, which runs:
- uv pip install --system $EXTRA_PIP_PACKAGES
So you can use it with a private index the same ways you would with pip. Recommended approaches:
- Preferred: configure the index via env vars and keep EXTRA_PIP_PACKAGES to package names only
job_variables={
"env": {
"PIP_INDEX_URL": "<https://__token_>_:{{ prefect.blocks.secret.my-pip-token }}@pypi.mycorp.com/simple",
# or add an additional index:
# "PIP_EXTRA_INDEX_URL": "<https://pypi.org/simple>",
# if needed by your index cert:
# "PIP_TRUSTED_HOST": "<http://pypi.mycorp.com|pypi.mycorp.com>",
"EXTRA_PIP_PACKAGES": "myprivpkg==1.2.3 anotherpkg>=0.5"
}
}
- Store the token in a Secret block and reference it so it isn’t exposed.
- These env vars are not echoed by the entrypoint; only the EXTRA_PIP_PACKAGES string is printed.
- Alternative (works but beware secrets in logs): pass pip flags directly in EXTRA_PIP_PACKAGES
"EXTRA_PIP_PACKAGES": "--index-url <https://__token_>_:TOKEN@pypi.mycorp.com/simple myprivpkg anotherpkg"
The entrypoint echoes the full command, so the token may appear in logs. Not recommended.
- Image-based options:
- Bake credentials into /etc/pip.conf (or mount one via your work pool’s base job template):
[global]
index-url = <https://__token_>_:TOKEN@pypi.mycorp.com/simple
trusted-host = <http://pypi.mycorp.com|pypi.mycorp.com>
Then EXTRA_PIP_PACKAGES: "myprivpkg anotherpkg" works with no secrets in env.
- Or pre-install deps in a custom image and skip EXTRA_PIP_PACKAGES.
Important note from earlier: since EXTRA_PIP_PACKAGES runs before your Git repo is cloned, don’t point it at a requirements file inside the repo. If you need a requirements file, copy it into the image and reference an absolute path (e.g., -r /opt/prefect/requirements.txt).
Reference:
- Entrypoint that runs `uv pip install`: entrypoint.sh
If you tell me which private index you’re using (PyPI token, Artifactory, Nexus, GitLab, etc.), I can give you an exact env snippet.