Martin Patz
11/18/2025, 9:07 PMworker-docker:
image: prefecthq/prefect:${PREFECT_TAG:-3-latest}
restart: unless-stopped
depends_on:
server:
condition: service_started
networks:
- internal
environment:
EXTRA_PIP_PACKAGES: prefect-docker
PREFECT_API_URL: <http://server:4200/api>
command: prefect worker start --pool whale --type docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
I have also already created a block named my-cr-secret that is a dump of DockerRegistryCredentials
credentials = DockerRegistryCredentials(
username=docker_user, password=SecretStr(docker_password), registry_url=docker_registry_url
)
credentials.save(name="my-cr-secret", overwrite=True)
now when I want to deploy some local code to run inside a docker container on my server, I fail at authentication. How do I provide the credentials or better reference the credentials in my-cr-secret?Marvin
11/18/2025, 9:07 PMMarvin
11/18/2025, 9:23 PMDockerRegistryCredentials block (my-cr-secret), reference it in your deployment’s job variables so the worker can log in before pulling the image.
Two places you may need credentials:
1) Runtime pull by the worker (always needed for private images)
2) Build/push during deploy steps (only if you build/push an image as part of deployment)
Option A — set credentials per deployment (recommended)
- Python deploy:
from prefect import flow
@flow
def my_flow():
...
if __name__ == "__main__":
my_flow.deploy(
name="my-deployment",
work_pool_name="whale", # your Docker work pool
image="<http://my-registry.example.com/my-app:latest|my-registry.example.com/my-app:latest>",
job_variables={
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}"
},
)
- prefect.yaml:
deployments:
- name: my-deployment
entrypoint: path/to/flow.py:my_flow
work_pool:
name: whale
job_variables:
image: <http://my-registry.example.com/my-app:latest|my-registry.example.com/my-app:latest>
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}"
Option B — set credentials on the work pool (applies to all deployments)
- In the Docker work pool’s base job template, set:
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}"
Then you don’t need to repeat it in each deployment.
If you’re also building/pushing an image during deployment (prefect.yaml steps), pass the same block to the push step:
build:
- prefect_docker.deployments.steps.build_docker_image:
id: build-image
image_name: <http://my-registry.example.com/my-app|my-registry.example.com/my-app>
tag: latest
dockerfile: auto
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: "{{ build-image.image_name }}"
tag: "{{ build-image.tag }}"
credentials: "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}"
Notes and gotchas
- The block reference format is: {{ prefect.blocks.docker-registry-credentials.<block-name> }} — so your value is {{ prefect.blocks.docker-registry-credentials.my-cr-secret }}.
- Ensure the block is saved to the same Prefect API your worker talks to (you’ve set `PREFECT_API_URL=http://server:4200/api`).
- registry_url should be the hostname only (no scheme). Examples:
- Docker Hub: omit registry_url or use <http://index.docker.io|index.docker.io>
- GHCR: <http://ghcr.io|ghcr.io>
- ACR: <http://myregistry.azurecr.io|myregistry.azurecr.io>
- Your worker image already installs prefect-docker via EXTRA_PIP_PACKAGES, which is required for the Docker worker type.
If auth still fails, please share:
- The registry you’re using
- How you deploy (Python, prefect.yaml, or CLI)
- The exact error message from the worker logs
References
- DockerRegistryCredentials block docs
- Docker deployments and steps (build/push)Martin Patz
11/18/2025, 9:47 PMMarvin
11/18/2025, 9:53 PMMarvin
11/18/2025, 9:58 PM--type docker on pool whale (as in your compose)
- The DockerRegistryCredentials block my-cr-secret exists in the same Prefect API your worker uses (`PREFECT_API_URL=http://server:4200/api`)
- The block type for prefect-docker is registered (one-time)
Step 0 — Point your CLI at the same API (important)
Run these commands from a shell where PREFECT_API_URL points to your server:
export PREFECT_API_URL=<http://server:4200/api>
prefect version
prefect server status
Step 1 — Ensure the Docker block type is registered (one-time)
prefect block register -m prefect_docker.credentials
Verify your credentials block is there:
prefect block inspect docker-registry-credentials/my-cr-secret
Step 2 — Create a base job template file for the pool
Create a file named base_job_template.json with this content. It sets the registry credentials at the pool level using your block by name and keeps everything else simple.
{
"job_configuration": {
"name": "{{ name }}",
"image": "{{ image }}",
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}",
"stream_output": true,
"auto_remove": true
},
"variables": {
"type": "object",
"properties": {
"name": {
"title": "Container Name",
"type": "string",
"default": "prefect-flow-run"
},
"image": {
"title": "Docker Image",
"description": "The image to run for your flow",
"type": "string",
"default": "<http://my-registry.example.com/my-app:latest|my-registry.example.com/my-app:latest>"
}
},
"required": [
"image"
]
}
}
Notes:
- The credentials line must be exactly the placeholder (no extra text): {{ prefect.blocks.docker-registry-credentials.my-cr-secret }}
- my-cr-secret is your existing block name
- Set the default image to your private image if you want
Step 3 — Update your existing work pool “whale” with this template
prefect work-pool update whale --base-job-template base_job_template.json
You can double-check the pool:
prefect work-pool inspect whale
No worker restart needed — new runs will use the updated template automatically.
Step 4 — Deploy normally without repeating credentials
Now you can deploy with just an image and pool; no need to pass registry_credentials per deployment.
Python:
from prefect import flow
@flow
def my_flow():
print("hello")
if __name__ == "__main__":
my_flow.deploy(
name="my-deployment",
work_pool_name="whale",
image="<http://my-registry.example.com/my-app:latest|my-registry.example.com/my-app:latest>"
)
prefect.yaml:
deployments:
- name: my-deployment
entrypoint: path/to/flow.py:my_flow
work_pool:
name: whale
job_variables:
image: <http://my-registry.example.com/my-app:latest|my-registry.example.com/my-app:latest>
Step 5 — Run a quick test
- Trigger a run (from UI or prefect deployment run my-deployment)
- Watch the worker logs; you should see it logging into your registry and pulling the image
Common gotchas
- Make sure the block’s registry_url is just the host (no scheme): e.g. <http://ghcr.io|ghcr.io>, <http://index.docker.io|index.docker.io>, <http://myregistry.azurecr.io|myregistry.azurecr.io>
- Your CLI and your worker must point to the same Prefect API; otherwise the worker won’t see the block you created
- Your worker container already has prefect-docker via EXTRA_PIP_PACKAGES, which is required for Docker workers
If you also build/push images during deployment
Putting credentials at the pool level handles runtime pulls. If you’re building/pushing as part of deployment steps, add the same block to your push step:
```
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: "{{ build-image.image_name }}"
tag: "{{ build-image.tag }}"Marvin
11/18/2025, 9:58 PMMartin Patz
11/18/2025, 10:04 PMResponse: {'exception_message': 'Invalid request received.', 'exception_detail': [{'type': 'value_error', 'loc': ['body', 'base_job_template'], 'msg': 'Value error, The variables specified in the job configuration template must be present as properties in the variables schema. Your job configuration uses the following undeclared variable(s): prefect.blocks.docker-registry-credentials.my-cr-secret.', 'input': {'job_configuration': {'name': '{{ name }}', 'image': '{{ image }}', 'registry_credentials': '{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}', 'stream_output': True, 'auto_remove': True}, 'variables': {'type': 'object', 'properties': {'name': {'title': 'Container Name', 'type': 'string', 'default': 'prefect-flow-run'}, 'image': {'title': 'Docker Image', 'description': 'The image to run for your flow', 'type': 'string', 'default': 'my-cr.tld/hello-world:latest'}}, 'required': ['image']}}, 'ctx': {'error': {}}}], 'request_body': {'base_job_template': {'job_configuration': {'name': '{{ name }}', 'image': '{{ image }}', 'registry_credentials': '{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}', 'stream_output': True, 'auto_remove': True}, 'variables': {'type': 'object', 'properties': {'name': {'title': 'Container Name', 'type': 'string', 'default': 'prefect-flow-run'}, 'image': {'title': 'Docker Image', 'description': 'The image to run for your flow', 'type': 'string', 'default': 'my-cr.tld/hello-world:latest'}}, 'required': ['image']}}}}
For more information check: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422>
An exception occurred.Marvin
11/18/2025, 10:04 PMMarvin
11/18/2025, 10:04 PM{{ ... }} as a variable name, so it expects to find a matching entry in the variables schema. Since we put the block reference directly in job_configuration, it complained.
You have two clean fixes. Pick one.
Option 1 (recommended): Declare a variable and set its default to your block reference
- Put registry_credentials in the variables schema
- Reference that variable in the job configuration
base_job_template.json
{
"job_configuration": {
"name": "{{ name }}",
"image": "{{ image }}",
"registry_credentials": "{{ registry_credentials }}",
"stream_output": true,
"auto_remove": true
},
"variables": {
"type": "object",
"properties": {
"name": {
"title": "Container Name",
"type": "string",
"default": "prefect-flow-run"
},
"image": {
"title": "Docker Image",
"description": "The image to run for your flow",
"type": "string",
"default": "my-cr.tld/hello-world:latest"
},
"registry_credentials": {
"title": "Registry Credentials",
"description": "DockerRegistryCredentials block reference",
"default": "{{ prefect.blocks.docker-registry-credentials.my-cr-secret }}"
}
},
"required": [
"image"
]
}
}
Update the pool:
export PREFECT_API_URL=<http://server:4200/api>
prefect work-pool update whale --base-job-template base_job_template.json
prefect work-pool inspect whale
Why this works:
- job_configuration now uses a declared variable {{ registry_credentials }}
- The variable’s default is your block reference, which Prefect resolves at runtime
Option 2: Inline a block reference by ID (no variables entry needed)
- Replace the credentials field with a direct $ref using the block’s UUID
Find the block’s ID:
prefect block inspect docker-registry-credentials/my-cr-secret
Copy the block_document_id/ID from the output, then use:
base_job_template.json
{
"job_configuration": {
"name": "{{ name }}",
"image": "{{ image }}",
"registry_credentials": {
"$ref": {
"block_document_id": "YOUR-BLOCK-UUID-HERE"
}
},
"stream_output": true,
"auto_remove": true
},
"variables": {
"type": "object",
"properties": {
"name": {
"title": "Container Name",
"type": "string",
"default": "prefect-flow-run"
},
"image": {
"title": "Docker Image",
"type": "string",
"default": "my-cr.tld/hello-world:latest"
}
},
"required": [
"image"
]
}
}
Update the pool:
export PREFECT_API_URL=<http://server:4200/api>
prefect work-pool update whale --base-job-template base_job_template.json
Quick test
- Deploy a flow that uses an image from your private registry and targets work_pool_name="whale"
- Trigger a run and watch worker logs for successful login/pull
Notes
- Keep registry_url as a host only (e.g., <http://ghcr.io|ghcr.io>, <http://index.docker.io|index.docker.io>, <http://myregistry.azurecr.io|myregistry.azurecr.io>)
- Your CLI and worker must point to the same API (`PREFECT_API_URL=http://server:4200/api`)
- If you haven’t already, register the docker blocks once:
prefect block register -m prefect_docker.credentials
Reference
- DockerRegistryCredentials block docs
If you still get a 422 after this change, paste your exact base_job_template.json and the failing command and I’ll spot-check it.