Ferchau Gupta
04/16/2026, 6:10 AMprefect.yaml file?Marvin
04/16/2026, 6:10 AMMarvin
04/16/2026, 6:15 AMpull step in prefect.yaml is only for fetching your flow code (e.g., from Git or remote storage) inside the running container. It does not control container image pulling. Image pulling is handled by Kubernetes via the pod spec.
How to run on K8s with a private ACR image instead
You have two common paths in Prefect 2.20:
1) Kubernetes work pool (prefect-kubernetes worker) – recommended
- Set the image in your deployment’s work_pool.job_variables.
- To use imagePullSecrets for ACR, add a variable and wire it into the base job template in the work pool’s Advanced settings.
Example prefect.yaml (after you’ve added image_pull_secrets to the pool’s template):
deployments:
- name: k8s
entrypoint: flows/hello.py:flow
work_pool:
name: my-k8s-pool
job_variables:
image: <http://myregistry.azurecr.io/my-image:latest|myregistry.azurecr.io/my-image:latest>
namespace: default
image_pull_policy: IfNotPresent
image_pull_secrets:
- name: acr-secret
Example base job template snippet (in the work pool’s Advanced editor) showing how to add and use `image_pull_secrets`:
{
"variables": {
"properties": {
"image": {
"type": "string"
},
"namespace": {
"type": "string",
"default": "default"
},
"image_pull_policy": {
"type": "string",
"enum": [
"IfNotPresent",
"Always",
"Never"
],
"default": "IfNotPresent"
},
"image_pull_secrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"default": []
}
}
},
"job_configuration": {
"job_manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"spec": {
"template": {
"spec": {
"imagePullSecrets": "{{ image_pull_secrets }}",
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"imagePullPolicy": "{{ image_pull_policy }}"
}
],
"restartPolicy": "Never"
}
}
}
}
}
}
2) Legacy KubernetesJob infrastructure block
- Set the image normally, and add imagePullSecrets via customizations (JSON patch to the pod spec):
from prefect.infrastructure.kubernetes import KubernetesJob
k8s = KubernetesJob(
image="<http://myregistry.azurecr.io/my-image:latest|myregistry.azurecr.io/my-image:latest>",
namespace="default",
image_pull_policy="IfNotPresent",
customizations=[
{
"op": "add",
"path": "/spec/template/spec/imagePullSecrets",
"value": [{"name": "acr-secret"}]
}
]
)
k8s.save("k8s-job", overwrite=True)
Create the Kubernetes secret for ACR (if not using AKS’s automatic integration):
kubectl create secret docker-registry acr-secret \
--docker-server=<http://myregistry.azurecr.io|myregistry.azurecr.io> \
--docker-username=<ACR_USERNAME> \
--docker-password=<ACR_PASSWORD_OR_TOKEN> \
--namespace=default
Tip for AKS + ACR
- If you run AKS, attaching ACR at the cluster level often removes the need to explicitly set `imagePullSecrets`:
az aks update -g <resource-group> -n <cluster-name> --attach-acr <acr-name>
- Docs: AKS + ACR integration
References
- KubernetesJob source (2.20): Kubernetes infrastructure
- Pull steps (code fetching, not image pulling): Pull steps
- Kubernetes worker (prefect-kubernetes): prefect-kubernetes integrationMarvin
04/16/2026, 6:15 AMKubernetesJob block right now? Also, are you on AKS with ACR attached, or do you need to reference a specific secret name?Ferchau Gupta
04/17/2026, 8:48 AMflow_name = flow._name_
flow_specific_work_pool_name = f"{flow_name}"
async with get_client() as client:
await client.create_work_pool(
work_pool=WorkPoolCreate(
name=flow_specific_work_pool_name,
type=worker.type,
base_job_template=worker._base_job_template,
),
overwrite=True
)Marvin
04/17/2026, 8:48 AMMarvin
04/17/2026, 8:52 AMfrom prefect.settings import get_current_settings
print(get_current_settings().api_url)
- No actual change was sent
- If worker._base_job_template is None or identical to what’s already stored, you won’t see a visible change in the UI.
- Confirm what was applied by reading it back after the call:
async with get_client() as client:
updated = await client.read_work_pool(flow_specific_work_pool_name)
print(updated.base_job_template)
- Silent warning not surfaced
- The “cannot overwrite type” warning may be easy to miss in async or non-verbose runs. You can force warnings to display:
import warnings
warnings.simplefilter("default")
What your code does on conflict with overwrite=True
- Prefect first tries to create the pool. If it already exists (409), it:
- Keeps the existing type (ignores the new type, with a warning)
- Updates only: description, is_paused, base_job_template, concurrency_limit
- Returns the updated pool
- Relevant code (2.20.0): OrionClient.create_work_pool and WorkPoolCreate/WorkPoolUpdate
Sanity-check snippet
from prefect.client.orchestration import get_client
from prefect.client.schemas.actions import WorkPoolCreate, WorkPoolUpdate
name = "my-pool"
wp_create = WorkPoolCreate(
name=name,
type="kubernetes", # Will NOT change if pool already exists with different type
base_job_template=worker._base_job_template, # Must be a dict; ensure not None
)
async with get_client() as client:
res = await client.create_work_pool(work_pool=wp_create, overwrite=True)
print("Returned type:", res.type)
# Verify server state
fetched = await client.read_work_pool(name)
print("Fetched base_job_template keys:", fetched.base_job_template.keys())
If you need to update only the base_job_template explicitly:
async with get_client() as client:
await client.update_work_pool(
work_pool_name=name,
work_pool=WorkPoolUpdate(base_job_template=worker._base_job_template),
)
Questions to pinpoint the cause
- Which field(s) are you expecting to change (type, base_job_template, description, etc.)?
- Are you certain your code points at the same Prefect workspace the UI is showing?
- Can you print the WorkPool returned by create_work_pool and confirm the values there?
- What does worker._base_job_template contain (and is it a non-empty dict)?