Ishan Anilbhai Koradiya
03/04/2026, 8:44 AMMarvin
03/04/2026, 8:44 AMMarvin
03/04/2026, 8:49 AMprefect work-pool get-default-base-job-template --type kubernetes > template.json
2) In template.json, set:
"variables": {
"image": {
"default": "<http://my-registry.com/my-team/my-image:tag|my-registry.com/my-team/my-image:tag>"
}
}
3) Apply it to your pool
prefect work-pool update "YOUR_POOL_NAME" --base-job-template template.json
Option B — Override per deployment (recommended if you only want some jobs to use the custom image)
- Using prefect.yaml:
deployments:
- name: my-deployment
entrypoint: flows/your_flow.py:your_flow
work_pool:
name: YOUR_POOL_NAME
job_variables:
image: "<http://my-registry.com/my-team/my-image:tag|my-registry.com/my-team/my-image:tag>"
# optional: image_pull_policy: "Always"
Then deploy:
prefect deploy -n my-deployment
- Using Python:
from prefect import flow
@flow
def your_flow():
...
your_flow.from_source(
source="<https://github.com/your-org/your-repo.git>",
entrypoint="flows/your_flow.py:your_flow"
).deploy(
name="my-deployment",
work_pool_name="YOUR_POOL_NAME",
job_variables={
"image": "<http://my-registry.com/my-team/my-image:tag|my-registry.com/my-team/my-image:tag>",
# optional: "image_pull_policy": "Always",
},
)
Precedence
- Deployment job_variables override the work pool default. If neither is set, Prefect falls back to the default Prefect image.
Private registries
- If your image is in a private registry, add imagePullSecrets to the base job template (via Work Pool base job template JSON). See the “Customize base job templates” guide below.
Helpful docs
- Kubernetes worker/infrastructure: Run flows on Kubernetes
- Customize base job templates: Customize base job templates
- Customize job variables (per-deployment overrides): Customize job variables
- prefect-kubernetes integration overview: prefect-kubernetes integration
Want me to help you set this as a pool-wide default or just for specific deployments? If you share your pool name and the exact image (and whether it’s private), I can give you the exact snippet/command to use.Ishan Anilbhai Koradiya
03/04/2026, 9:59 AMMarvin
03/04/2026, 9:59 AMMarvin
03/04/2026, 10:02 AMjob_variables to pass an env var for runtime installs, but that’s optional.
Your options:
1) Bake deps into the image (recommended for production)
- If you already have a custom image on top of prefecthq/prefect:..., install your deps there. Then point the deployment to that image via job_variables.image.
- With prefect.yaml auto-build (picks up requirements.txt):
build:
- prefect_docker.deployments.steps.build_docker_image:
id: build-image
requires: prefect-docker>=0.4.0
image_name: my-registry/my-image
tag: latest
dockerfile: auto # auto Dockerfile installs requirements.txt
deployments:
- name: my-deployment
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: kubernetes
job_variables:
image: "{{ build-image.image }}"
2) Quick runtime installs via job variables (good for dev/testing)
- Set EXTRA_PIP_PACKAGES under env in `job_variables`:
deployments:
- name: my-deployment
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: kubernetes
job_variables:
env:
EXTRA_PIP_PACKAGES: "pandas==2.2.2 boto3>=1.28.0"
- The worker will run uv pip install for those packages at job start (adds overhead each run).
3) Custom Dockerfile with Python API (advanced/private indexes, build args)
- Use DockerImage in .deploy() to control the build exactly (Dockerfile, build args, private registries).
Docs to dive deeper:
- Customize job variables: Customize job variables
- Deploy with prefect.yaml (build/push/deploy): prefect.yaml deployments
- Deploy via Python (using `image`/`DockerImage`): Deploy via Python
- Docker/Kubernetes infra guides: Run flows with Docker and Run flows on Kubernetes
If you share how you prefer to manage deps (prebaked image vs. runtime installs) and where your code lives (local vs. Git), I can give you a minimal, exact snippet for your setup.Ishan Anilbhai Koradiya
03/04/2026, 11:12 AMMarvin
03/04/2026, 11:12 AMMarvin
03/04/2026, 11:15 AMecho $PREFECT_SERVER_API_AUTH_STRING
- In your deployment (via job variables), pass it down:
work_pool:
name: YOUR_POOL
job_variables:
env:
PREFECT_API_URL: "<http://prefect-server-external.prefect.svc.cluster.local:4200/api>"
PREFECT_API_AUTH_STRING: "admin:password" # match the server’s value
Note: For production, use a Kubernetes Secret and a customized base job template with valueFrom; job_variables.env only supports plain strings.
2) Make sure PREFECT_API_KEY is NOT set in the flow-run container
- API keys are for Prefect Cloud. If both are present, API_KEY takes precedence and self-hosted auth will fail. Remove it from your image/env:
unset PREFECT_API_KEY
3) Verify your work pool’s base job template includes env passthrough
- The container spec must include "env": "{{ env }}". If you removed or replaced this, Prefect settings won’t be injected into the pod.
- Check the template:
prefect work-pool inspect YOUR_POOL
prefect work-pool get-default-base-job-template --type kubernetes
If you customized it, ensure the pod/container has "env": "{{ env }}" so Prefect settings (including PREFECT_API_URL and PREFECT_API_AUTH_STRING) are injected.
4) Inspect a failing pod’s environment
- Confirm the pod actually has the right settings:
kubectl -n <ns> describe pod <pod-name> | sed -n '/Environment:/,/Mounts:/p'
Look for:
- PREFECT_API_URL = http://prefect-server-external.prefect.svc.cluster.local:4200/api
- PREFECT_API_AUTH_STRING = your “user:password” (if server uses auth)
- Ensure PREFECT_API_KEY is not present
5) Version/TLS checks (less likely here)
- Keep client (your custom image’s Prefect version) and server reasonably aligned.
- If you use TLS, use WSS and set these as needed:
PREFECT_API_TLS_INSECURE_SKIP_VERIFY=true
PREFECT_API_SSL_CERT_FILE=/path/to/ca.pem
Why this happens with a custom image
- The image change doesn’t affect network reachability (you’re connecting) but can change what env vars exist in the container. If PREFECT_API_AUTH_STRING isn’t set (or PREFECT_API_KEY is set mistakenly), the event-stream handshake fails.
If you want a quick test
- Add this to your deployment’s job_variables temporarily, redeploy, and re-run:
job_variables:
env:
PREFECT_API_URL: "<http://prefect-server-external.prefect.svc.cluster.local:4200/api>"
PREFECT_API_AUTH_STRING: "admin:password"
If logs start streaming, move this into a more secure setup (K8s Secret + base job template with valueFrom).
Docs
- Customize job variables (env overrides): Customize job variables
- Customize base job templates (add imagePullSecrets, valueFrom, etc.): Customize base job templates
- Run flows on Kubernetes: Run flows on Kubernetes
Can you confirm:
- Are you using self-hosted Prefect Server (not Cloud)?
- Is PREFECT_SERVER_API_AUTH_STRING set on your server?
- Have you customized the work pool’s base job template?
- What do you see for PREFECT_API_* env vars inside a failing pod?Ishan Anilbhai Koradiya
03/04/2026, 11:28 AMMarvin
03/04/2026, 11:28 AMMarvin
03/04/2026, 11:30 AM"env": "{{ env }}")
- You’re not actually talking to the server you think you are (wrong URL/ingress), or there’s a version mismatch causing the handshake to be treated like an auth failure
Here’s how to pinpoint it quickly:
1) Confirm what server you’re hitting and whether it expects auth
- From a failing flow-run pod:
# See where the SDK is connecting
env | grep PREFECT_API_URL
# Optional: quick reachability check (will not “work” but should show no 401)
curl -i <http://prefect-server-external.prefect.svc.cluster.local:4200/api/events/in>
- On the Prefect Server pod:
# If this prints anything non-empty, auth is enabled on the server
printenv PREFECT_SERVER_API_AUTH_STRING
If that var is set, either unset it on the server or set the matching client var in your flow-run pods:
PREFECT_API_AUTH_STRING="user:password"
2) Make sure your pod actually has Prefect settings injected
- If your work pool base job template was customized, verify the container spec includes:
"env": "{{ env }}"
Without that, the pod won’t inherit Prefect settings (including API URL/auth).
- Inspect your pool and compare with the default:
prefect work-pool inspect YOUR_POOL
prefect work-pool get-default-base-job-template --type kubernetes
- You can also check a failing pod:
kubectl -n <ns> describe pod <pod> | sed -n '/Environment:/,/Mounts:/p'
3) Rule out version/endpoint mismatch
- What Prefect version is your server? If you’re running a Prefect 2.x server with 3.x workers/flows, the event stream endpoint/handshake won’t match and can look like an auth failure. If that’s the case, upgrade the server to 3.x or align versions.
- You can also enable debug logs for more detail:
PREFECT_DEBUG_MODE=1
If you need a quick test to confirm it’s purely auth/string-related, add this temporarily to your deployment:
work_pool:
name: YOUR_POOL
job_variables:
env:
PREFECT_API_URL: "<http://prefect-server-external.prefect.svc.cluster.local:4200/api>"
PREFECT_API_AUTH_STRING: "user:password" # only if server actually has it set
If that resolves it, move the value into a Kubernetes Secret and reference it via a customized base job template (valueFrom).
Helpful references (for env passthrough and templates):
- Customize base job templates (ensure "env": "{{ env }}" is present): Customize base job templates
- Kubernetes worker/infrastructure: Run flows on Kubernetes
- Customize job variables (for temporary env overrides): Customize job variables
If you can share:
- Output of printenv PREFECT_SERVER_API_AUTH_STRING from the server pod
- Prefect version of your server
- A snippet of your work pool base job template (the containers[0] section)
…I can tell you exactly which fix applies.Ishan Anilbhai Koradiya
03/04/2026, 11:39 AMPREFECT_SERVER_API_AUTH_STRING
server
i dont understand why its asking for an auth token
this is my custom docker image that i am using in as my job variable
FROM prefecthq/prefect:3.1.15-python3.10
# Define common paths as build args and env vars
ARG DBT_VENV="/home/ddp/dbt"
ARG CLIENTDBT_ROOT="/mnt/appdata/clientdbts"
ENV DBT_VENV=${DBT_VENV}
ENV CLIENTDBT_ROOT=${CLIENTDBT_ROOT}
# Install system dependencies
RUN apt-get update && apt-get install -y \
python3-venv \
python3-dev \
git \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install additional Prefect packages
RUN pip install git+https://github.com/Ishankoradia/prefect-airbyte.git@v0.90 prefect-dbt==0.6.6
# Create the exact directory structure matching your current setup
RUN mkdir -p ${DBT_VENV} \
&& mkdir -p ${CLIENTDBT_ROOT}
# Copy dbt version directories with their lock files
COPY dbt-1.9.8/ /tmp/dbt-1.9.8/
COPY dbt-1.8.7/ /tmp/dbt-1.8.7/
COPY dbt-1.10.19/ /tmp/dbt-1.10.19/
# Create dbt 1.8.7 venv (default - matches your current venv path)
RUN python3 -m venv /home/ddp/dbt/venv && \
cd /tmp/dbt-1.8.7 && \
/home/ddp/dbt/venv/bin/pip install --no-cache-dir . && \
rm -rf /tmp/dbt-1.8.7
# Create dbt 1.9.8 venv
RUN python3 -m venv /home/ddp/dbt/venv-1.9.8 && \
cd /tmp/dbt-1.9.8 && \
/home/ddp/dbt/venv-1.9.8/bin/pip install --no-cache-dir . && \
rm -rf /tmp/dbt-1.9.8
# Create dbt 1.10.19 venv
RUN python3 -m venv /home/ddp/dbt/venv-1.10.19 && \
cd /tmp/dbt-1.10.19 && \
/home/ddp/dbt/venv-1.10.19/bin/pip install --no-cache-dir . && \
rm -rf /tmp/dbt-1.10.19
# Final cleanup - remove build tools and any remaining artifacts
RUN apt-get purge -y build-essential python3-dev curl && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* /root/.cache /tmp/* /var/tmp/*Marvin
03/04/2026, 11:39 AMMarvin
03/04/2026, 11:43 AMPREFECT_SERVER_API_AUTH_STRING is null on your server, Prefect Server itself is not requiring Basic auth. In that case, this “Unable to authenticate to the event stream” usually points to one of these:
- Your flow-run pod is talking to an endpoint that adds its own auth (Ingress/external Service), not the bare Server API
- A Cloud-style API key is present in the pod and the client is attempting the wrong auth scheme
- Env injection in the job template is off, or there’s a version/endpoint mismatch
Given your ws URL is:
ws://prefect-server-external.prefect.svc.cluster.local:4200/api/events/in
That “-external” service name is a strong hint: you’re likely hitting an external/ingress-facing Service that may be enforcing auth or rewriting WebSocket traffic. Inside the cluster, you should target the internal ClusterIP Service for the API to avoid extra auth and WS proxying issues.
What to try first
1) Point runs to the internal service
- Set PREFECT_API_URL to the internal service name (adjust namespace/name as needed):
<http://prefect-server.prefect.svc.cluster.local:4200/api>
- Add it via deployment job variables or in the work pool base job template so it flows into the pod:
work_pool:
name: YOUR_POOL
job_variables:
env:
PREFECT_API_URL: "<http://prefect-server.prefect.svc.cluster.local:4200/api>"
2) Ensure no Cloud API key is present
- Inside a failing pod, check:
env | grep PREFECT_API_
Confirm:
- PREFECT_API_KEY is NOT set (Cloud-only; if present it can trigger the wrong auth path)
- PREFECT_API_AUTH_STRING is empty (fine for self-hosted without auth)
- PREFECT_API_URL points to the internal service as above
3) Verify env passthrough in your work pool template
- Your container spec should include:
"env": "{{ env }}"
If it’s missing/overridden, Prefect settings won’t reach the pod. Compare your pool to the default:
prefect work-pool inspect YOUR_POOL
prefect work-pool get-default-base-job-template --type kubernetes
4) Quick connectivity check from the pod
- These commands won’t fully “work” but will show status codes (401/403 here would confirm an auth layer on the external path):
curl -i <http://prefect-server-external.prefect.svc.cluster.local:4200/api/health>
curl -i <http://prefect-server-external.prefect.svc.cluster.local:4200/api/events/in>
curl -i <http://prefect-server.prefect.svc.cluster.local:4200/api/health>
5) Keep versions aligned
- Check server and custom image Prefect versions; try to keep them close (you’re on 3.1.15 in the image). If your server is older/newer by a lot, align them.
Your Dockerfile looks fine with respect to Prefect networking/auth; nothing there would trigger this specific error.
If you can share:
- The output of env | grep PREFECT_API_ from a failing pod
- The Kubernetes work pool base job template containers[0] snippet (to confirm "env": "{{ env }}")
- Your server Service names/namespace (so we can confirm the correct internal service URL)
…I can give you an exact job_variables or pool template patch. For reference:
- Customize base job templates: Customize base job templates
- Customize job variables (env overrides): Customize job variables
- Run flows on Kubernetes: Run flows on KubernetesIshan Anilbhai Koradiya
03/04/2026, 1:38 PMMarvin
03/04/2026, 1:38 PMMarvin
03/04/2026, 1:39 PMPREFECT_API_URL=<http://prefect-server.prefect.svc.cluster.local:4200/api>
Set it on your deployment (or make it pool-wide) so it flows into the pod:
work_pool:
name: YOUR_POOL
job_variables:
env:
PREFECT_API_URL: "<http://prefect-server.prefect.svc.cluster.local:4200/api>"
Then verify inside a failing pod:
env | grep PREFECT_API_
# Expect:
# PREFECT_API_URL=<http://prefect-server.prefect.svc.cluster.local:4200/api>
# PREFECT_API_KEY not set
# PREFECT_API_AUTH_STRING not set (fine, since server has none)
Also double-check your work pool base job template still passes env through:
prefect work-pool get-default-base-job-template --type kubernetes
# In containers[0], ensure you have:
# "env": "{{ env }}"
If that’s missing in a customized template, Prefect settings won’t reach the pod.
If you must use the external/ingress route
- Ensure your ingress routes /api/events/in to the Prefect API service and allows websocket upgrades on that path (no extra auth). For NGINX, confirm websocket support and timeouts; for Traefik/other gateways, ensure Upgrade/Connection headers are preserved.
Other sanity checks
- Keep server and image Prefect versions close (you’re on 3.1.15 in the image; match the server if possible).
- Server-side event streaming is enabled by default; if it was disabled, the server could close the socket early. You can confirm on the server pod:
printenv PREFECT_SERVER_EVENTS_STREAM_OUT_ENABLED
# should be empty or "true"
If you share:
- The Service names/namespace of your Prefect API,
- The output of env | grep PREFECT_API_ from a failing flow-run pod,
- The containers[0] section of your pool’s base job template,
…I’ll give you the exact job_variables or template patch to apply.
Helpful refs:
- Run flows on Kubernetes
- Customize base job templates
- Customize job variables