oleander-whirlwind
12/10/2025, 8:19 AMMarvin
12/10/2025, 8:19 AMMarvin
12/10/2025, 8:22 AMPREFECT_API_URL to the endpoint your CI can reach (e.g., `http://somehost:4200/api`). This is only needed to create/update the deployment.
- In your worker container (runtime), set PREFECT_API_URL to the address resolvable inside the Docker network (e.g., `http://prefect-server:4200/api`). Workers automatically pass their Prefect settings to flow run containers, so your flows will connect using the worker’s URL. No need to inject it in your flow code.
Example: Docker Compose worker
services:
prefect-worker:
image: prefecthq/prefect:3-latest
environment:
PREFECT_API_URL: "<http://prefect-server:4200/api>"
# For Prefect Cloud use PREFECT_API_KEY; for self-hosted with auth use PREFECT_API_AUTH_STRING if applicable
# PREFECT_API_KEY: "pnu_..."
# PREFECT_API_AUTH_STRING: "admin:password"
command: >
prefect worker start -p my-work-pool
depends_on:
- prefect-server
networks:
- prefect
CI (deploy) example
# environment for your CI job
PREFECT_API_URL=<http://somehost:4200/api>
# plus auth if needed:
# PREFECT_API_KEY=...
# or PREFECT_API_AUTH_STRING=...
# then run either:
prefect deploy
# or in Python: flow.from_source(...).deploy(...)
If you ever need to override the API URL for a specific deployment (rare with a single server), you can set it per-deployment via `job_variables.env`:
flow.deploy(
name="my-deployment",
work_pool_name="my-pool",
job_variables={
"env": {
"PREFECT_API_URL": "<http://some-other-endpoint:4200/api>"
}
}
)
Key points:
- Use environment variables, not code, for PREFECT_API_URL.
- The deployment process and the worker runtime can (and often should) use different URLs depending on their network vantage point.
- Flow run containers inherit the worker’s PREFECT_* settings automatically.
Does that match your setup (self-hosted Prefect Server + Docker worker)? If so, I can help tailor a minimal docker-compose and CI snippet for your exact stack.oleander-whirlwind
12/10/2025, 9:48 AMMarvin
12/10/2025, 9:48 AMMarvin
12/10/2025, 9:53 AMdocker login <your-registry>
docker pull <your-registry>/<repo>:<tag>
- If this pull succeeds, your worker should be able to pull too.
- Option B (recommended): Provide explicit registry credentials via a DockerRegistryCredentials block and reference it in your work pool job variables.
1) Install the docker integration and register block types:
pip install "prefect[docker]"
prefect block register -m prefect_docker
2) Create the credentials block (you can run this once locally or anywhere with Prefect API access):
from prefect_docker import DockerRegistryCredentials
from pydantic import SecretStr
creds = DockerRegistryCredentials(
username="YOUR_USERNAME",
password=SecretStr("YOUR_PASSWORD_OR_PAT"),
registry_url="<http://your-registry.example.com|your-registry.example.com>"
)
import asyncio; asyncio.run(creds.save("my-registry", overwrite=True))
3) In your Docker work pool’s job variables (UI or prefect.yaml), set:
work_pool:
name: your-docker-pool
job_variables:
image: <http://your-registry.example.com/your-repo:your-tag|your-registry.example.com/your-repo:your-tag>
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-registry }}"
Kubernetes worker
- Create a docker-registry secret in the namespace where jobs run and add it to the pool’s base job template as imagePullSecrets:
kubectl create secret docker-registry my-registry-secret \
--docker-server=<http://your-registry.example.com|your-registry.example.com> \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_PASSWORD_OR_PAT \
--docker-email=you@example.com \
-n <namespace>
Then in the Kubernetes work pool’s base job template:
spec:
template:
spec:
imagePullSecrets:
- name: my-registry-secret
ECS worker (AWS)
- Put your registry credentials in AWS Secrets Manager and reference them in the task definition via repositoryCredentials in the base job template:
containerDefinitions:
- name: prefect
image: "{{ image }}"
repositoryCredentials:
credentialsParameter: "arn:aws:secretsmanager:region:acct:secret:your-secret"
Azure Container Instances worker
- Provide an image registry block in job variables:
work_pool:
name: your-aci-pool
job_variables:
image: <http://yourregistry.azurecr.io/your-repo:tag|yourregistry.azurecr.io/your-repo:tag>
image_registry: "{{ prefect.blocks.docker-registry-credentials.my-registry }}"
Or use a managed identity block for ACR.
Helpful checks
- On the worker host, run:
docker pull <your-image>
If this fails, the worker will fail too—fix auth first.
- Make sure your token has the right scopes (e.g., GHCR: read:packages; ACR: pull; ECR: IAM permissions for GetAuthorizationToken/BatchGetImage).
If you can share your worker type and the registry you’re using, I’ll give you the exact snippet to drop into your work pool/deployment.oleander-whirlwind
12/10/2025, 10:07 AMMarvin
12/10/2025, 10:07 AMMarvin
12/10/2025, 10:08 AMrepositoryCredentials in the task definition template.
- Kubernetes: Use imagePullSecrets sourced from a K8s Secret that’s synced from your secret manager (e.g., External Secrets Operator or a CSI secrets driver). Don’t embed secrets in Prefect config.
3) Use Prefect Blocks for registry auth (most common, secure within Prefect)
- Store a DockerRegistryCredentials block and reference it from your work pool/deployment. Secret values are encrypted at rest in Prefect Cloud and redacted in logs.
- This keeps credentials out of code, CI logs, and your prefect.yaml.
Creating a DockerRegistryCredentials block (for Docker worker, ACI, and build/push steps)
pip install "prefect[docker]"
prefect block register -m prefect_docker
Then in Python (run once, anywhere with API access):
from prefect_docker import DockerRegistryCredentials
from pydantic import SecretStr
import asyncio
creds = DockerRegistryCredentials(
username="YOUR_USERNAME",
password=SecretStr("YOUR_PASSWORD_OR_PAT"),
registry_url="<http://your-registry.example.com|your-registry.example.com>"
)
asyncio.run(creds.save("my-registry", overwrite=True))
Reference it in your Docker work pool job variables (UI or prefect.yaml):
work_pool:
name: your-docker-pool
job_variables:
image: <http://your-registry.example.com/your-repo:tag|your-registry.example.com/your-repo:tag>
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-registry }}"
For build/push steps in prefect.yaml:
push:
- prefect_docker.deployments.steps.push_docker_image:
requires: prefect-docker>=0.3.0
image_name: "{{ build-image.image_name }}"
tag: "{{ build-image.tag }}"
credentials: "{{ prefect.blocks.docker-registry-credentials.my-registry }}"
Kubernetes worker best practice
- Prefer cloud identity; otherwise use an imagePullSecrets K8s Secret (ideally synced from your secret manager):
kubectl create secret docker-registry my-registry-secret \
--docker-server=<http://your-registry.example.com|your-registry.example.com> \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_PASSWORD_OR_PAT \
-n <namespace>
Add to the Kubernetes work pool’s base job template:
spec:
template:
spec:
imagePullSecrets:
- name: my-registry-secret
Azure ACI worker best practice
- Prefer managed identity:
# In job variables
image_registry: "{{ prefect.blocks.acr-managed-identity.YOUR_IDENTITY_BLOCK }}"
- Or use DockerRegistryCredentials as above.
ECS worker best practice
- Prefer ECR with IAM (no secrets).
- For other registries, use AWS Secrets Manager + repositoryCredentials in the task definition template.
What to avoid
- Hardcoding credentials in prefect.yaml or env vars.
- Relying on docker login on the host (works but is less auditable/portable and easy to misconfigure in CI).
If you share your worker type and registry (e.g., GHCR/ECR/ACR), I’ll give you the exact, minimal configuration following the most secure option for that stack.oleander-whirlwind
12/11/2025, 7:15 AMMarvin
12/11/2025, 7:15 AMMarvin
12/11/2025, 7:19 AM.deploy(). Instead:
- Code storage (Git, S3, etc.): Set via Flow.from_source(source=..., entrypoint=...) before calling .deploy(...)
- Infrastructure settings: Set on your work pool and optionally override via .deploy(job_variables=...)
- Result storage: Set on the flow via the @flow decorator (result_storage=...)
- Credentials blocks: Load them in your deploy.py (or inside the flow) and pass them to whatever needs them (e.g., a GitRepository, S3 client, etc.)
Signatures (verified):
Flow.from_source(source, entrypoint) -> Flow
Flow.deploy(name, work_pool_name=None, image=None, build=True, push=True, work_queue_name=None, job_variables=None, ...)
Clean, recommended patterns
1) Point your deployment at a repo using a credentials block (deploy.py)
from prefect.flows import Flow
from prefect.runner.storage import GitRepository
from prefect_github import GitHubCredentials
if __name__ == "__main__":
gh = GitHubCredentials.load("my-gh-token-block") # a Block
repo = GitRepository(
url="<https://github.com/org/repo.git>",
credentials=gh, # use the loaded block instance here
)
Flow.from_source(
source=repo,
entrypoint="flows/my_flow.py:my_flow", # path:callable_name in the repo
).deploy(
name="prod",
work_pool_name="docker-pool",
job_variables={
"env": {"ENV": "prod"},
# any pool template overrides like image/memory/cpu/etc.
# "image": "my-registry/my-image:latest",
},
)
2) Set result storage with a block on the flow (flow code)
from prefect import flow
from prefect_aws.s3 import S3Bucket
s3 = S3Bucket.load("my-s3-bucket-block")
@flow(result_storage=s3, persist_result=True)
def my_flow(name: str = "world"):
return f"Hello, {name}!"
Tip: If you want to avoid API calls at import time, you can defer block loading with a string slug:
@flow(result_storage="s3-bucket/my-s3-bucket-block", persist_result=True)
def my_flow(...):
...
Key takeaways
- Don’t pass blocks to .deploy() — it doesn’t accept them.
- Use Flow.from_source(...) to define code storage; you can pass a storage object that uses a credentials block.
- Use job_variables in .deploy(...) to tweak infra at deploy time.
- Use the flow decorator for result storage.
- Removed in 3.x: Deployment.build_from_flow() and prefect deployment build.
If you can share which block you’re trying to point to (e.g., GitHubCredentials, S3Bucket, Secret), I can tailor the exact snippet to your setup.oleander-whirlwind
12/11/2025, 7:21 AMMarvin
12/11/2025, 7:21 AMMarvin
12/11/2025, 7:26 AMregistry_credentials per deployment via job_variables
- You can either pass a loaded block object in Python, or reference a saved block via a template string
- Kubernetes/ECS don’t use this field; use their native mechanisms (imagePullSecrets / repositoryCredentials)
Examples for Docker worker
1) Pass a loaded block per deployment (cleanest in deploy.py)
from prefect import flow
from prefect_docker.credentials import DockerRegistryCredentials
@flow
def my_flow():
...
if __name__ == "__main__":
prod_creds = DockerRegistryCredentials.load("docker-registry-prod")
stg_creds = DockerRegistryCredentials.load("docker-registry-staging")
my_flow.deploy(
name="prod",
work_pool_name="docker-pool",
image="<http://my-registry.example.com/app:prod|my-registry.example.com/app:prod>",
job_variables={"registry_credentials": prod_creds},
)
my_flow.deploy(
name="staging",
work_pool_name="docker-pool",
image="<http://staging-registry.example.com/app:stg|staging-registry.example.com/app:stg>",
job_variables={"registry_credentials": stg_creds},
)
2) Use a block reference string (if you prefer templating)
my_flow.deploy(
name="prod",
work_pool_name="docker-pool",
image="<http://my-registry.example.com/app:prod|my-registry.example.com/app:prod>",
job_variables={
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.docker-registry-prod }}"
},
)
Notes
- Field name is registry_credentials on the Docker worker job config; it expects a DockerRegistryCredentials block.
- You can put a default in the work pool’s base job template and override per deployment with job_variables when needed.
- If you’re not on the Docker worker:
- Kubernetes: set imagePullSecrets in job_variables.job_manifest (K8s secret must exist in the cluster).
- ECS: set task_definition.containerDefinitions[].repositoryCredentials.credentialsParameter to an AWS Secrets Manager ARN.
Helpful links
- Prefect Docker integration reference
- Docker worker job config (source)
- DockerRegistryCredentials block (source)
- Worker templating/deserialization (source)
If you can confirm which worker you’re using (Docker, Kubernetes, ECS), I’ll tailor the exact snippet to that runtime.oleander-whirlwind
12/11/2025, 7:35 AMMarvin
12/11/2025, 7:35 AMMarvin
12/11/2025, 7:37 AMjob_variables={"registry_credentials": stg_creds}
- Pros:
- Convenient when scripting in Python; you already have the object.
- Type-safe locally (Pydantic validation happens right there).
- Cons:
- The object is serialized into the deployment payload. Sensitive fields (e.g., password) become plain JSON in the deployment record.
- Ties your deploy script to having API access to load the block at deploy time.
- Harder to see/edit from the UI because the deployment stores a blob of values instead of a neat reference.
Using a block reference (templated string):
job_variables={
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.docker-registry-prod }}"
}
- Pros:
- Only a reference is stored on the deployment; the actual secret stays in the block store and is resolved at runtime.
- Safer for credentials; no secret material in the deployment JSON.
- Easy to swap per environment by changing the block name, and easy to inspect/edit in the UI or prefect.yaml.
- Works even when your deploy script shouldn’t load blocks at import/deploy time.
- Cons:
- Slightly more ceremony (need the block pre-created with the correct name).
- You don’t get local type-checking on that field at deploy time.
Recommendation
- For credentials (registry, cloud auth, etc.), prefer the templated block reference approach to avoid leaking secrets into deployment config and to keep deployments portable and editable.
- Reserve passing objects directly for non-sensitive config or for quick local experiments.
If you share the worker type and how you manage environments (prod/stg/dev), I can sketch a pattern for naming and referencing the right block per deployment.oleander-whirlwind
12/11/2025, 7:58 AMoleander-whirlwind
12/11/2025, 7:58 AMMarvin
12/11/2025, 7:58 AMMarvin
12/11/2025, 8:00 AMregistry_credentials is a structured field (expects a DockerRegistryCredentials object).
- You passed a template string: {{ prefect.blocks.docker-registry-credentials.my-credentials }}.
- Prefect validates deployment payloads before saving; for some fields it checks type/shape. A raw string can be rejected when the server expects an object.
Why this can fail at deploy time
- Although block references are resolved later by the worker, the deployment creation still validates job_variables against the work pool’s base job template schema. If the schema for registry_credentials says “object”, providing a plain string (the template) can cause a validation error: Validation failed for field 'registry_credentials'. Failure reason: '{{ ... }}'.
Two reliable fixes
Option A: Pass the loaded block object in Python (works immediately at deploy time)
from prefect import flow
from prefect_docker.credentials import DockerRegistryCredentials
@flow
def my_flow():
...
if __name__ == "__main__":
creds = DockerRegistryCredentials.load("my-credentials")
my_flow.deploy(
name="staging",
work_pool_name="docker-pool",
image="my.private.registry/app:stg",
job_variables={"registry_credentials": creds},
)
Option B: Put the templated reference into the pool’s base job template or prefect.yaml so the deploy client doesn’t validate it as a typed field
- In prefect.yaml:
deployments:
- name: staging
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: docker-pool
job_variables:
image: "my.private.registry/app:stg"
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-credentials }}"
- Or set this default in the work pool’s base job template in the UI, then omit registry_credentials from job_variables at deploy time. The worker will resolve the block reference at run time.
Checklist to avoid this error
- Ensure the block type slug is correct: docker-registry-credentials (hyphenated, lowercase).
- Use the block document name exactly as saved in Prefect.
- If you must pass the reference via .deploy(job_variables=...), prefer Option A (pass the loaded object) to satisfy deploy-time validation.
- If you want purely declarative (no Python block loading), place the {{ ... }} in prefect.yaml or the pool template.
If you share how you’re deploying (Python script vs prefect.yaml) and whether you can edit the work pool template, I can suggest the cleanest path for your setup.oleander-whirlwind
12/11/2025, 9:13 AMMarvin
12/11/2025, 9:14 AMMarvin
12/11/2025, 9:15 AMdocker login <registry> -u <username>
# paste the same password/token as in your block
If this fails, fix creds/URL first.
2) Verify the registry_url in your credentials block matches your registry:
- Docker Hub: leave blank or use https://index.docker.io/v1/
- GHCR: ghcr.io
- GitLab: registry.gitlab.com
- ACR: <name>.azurecr.io
- ECR: <account>.dkr.ecr.<region>.amazonaws.com (password must be an AWS ECR login password from aws ecr get-login-password, expires after 12h)
3) If your registry is HTTP or uses a self‑signed cert, add it to Docker’s daemon config as an insecure registry and restart Docker:
- /etc/docker/daemon.json:
{
"insecure-registries": [
"my-registry:5000"
]
}
4) Confirm the worker can reach the registry (proxy/firewall). If behind a proxy, set env vars in job_variables:
job_variables={"env": {"HTTP_PROXY": "...", "HTTPS_PROXY": "...", "NO_PROXY": "..." }}
5) Double‑check you’re not passing a raw string where an object is expected. For Docker workers, registry credentials must resolve to a DockerRegistryCredentials object at runtime. Either:
- Pass the loaded block object in Python:
creds = DockerRegistryCredentials.load("my-credentials")
my_flow.deploy(..., job_variables={"registry_credentials": creds})
- Or reference a saved block in prefect.yaml or pool template:
registry_credentials: "{{ prefect.blocks.docker-registry-credentials.my-credentials }}"
Provider-specific notes
- Docker Hub: if you use a PAT, set username to your Docker ID and registry_url to https://index.docker.io/v1/ (or leave empty).
- GHCR: use a classic PAT with the right scopes, registry_url="ghcr.io".
- ECR: don’t store a static password in the block; it will expire. Either:
- Refresh the block regularly with a new aws ecr get-login-password, or
- Use a public image, or
- Switch to ECS worker and use repositoryCredentials via AWS Secrets Manager for better ergonomics.
If it still fails, please share:
- Which registry you’re using
- The relevant part of your deployment’s job_variables (redact secrets)
- The values of username and registry_url from your DockerRegistryCredentials block (redact username if needed)
- A snippet of the Docker worker logs around “Logging into Docker registry...”
Helpful links:
- Prefect Docker integration reference
- Docker worker login code (source)oleander-whirlwind
12/11/2025, 11:45 AMMarvin
12/11/2025, 11:45 AMMarvin
12/11/2025, 11:46 AMDockerRegistryCredentials block also requires username and password. If you have a “token-only” auth model, most registries still expect you to supply a specific username value and put the token in the password field.
Use these provider-specific conventions:
- Docker Hub: username = your Docker ID; password = your PAT; registry_url = https://index.docker.io/v1/ (or leave blank)
- GHCR (ghcr.io): username = your GitHub username; password = your PAT; registry_url = ghcr.io
- GitLab Registry: username = oauth2; password = your token (PAT/CI_JOB_TOKEN); registry_url = registry.gitlab.com
- GCR/Artifact Registry: username = oauth2accesstoken; password = access token; registry_url = gcr.io or REGION-docker.pkg.dev (tokens expire, so you must refresh)
- ECR: username = AWS; password = output of aws ecr get-login-password (expires ~12h); registry_url = <account>.dkr.ecr.<region>.amazonaws.com
- ACR: username = registry admin user or service principal appId; password = admin key or SP secret; registry_url = <name>.azurecr.io
Example block for GHCR:
from prefect_docker.credentials import DockerRegistryCredentials
creds = DockerRegistryCredentials(
username="my-gh-username",
password="ghp_xxx", # your PAT
registry_url="<http://ghcr.io|ghcr.io>",
)
await creds.save("ghcr-prod")
Then in your deployment:
job_variables={
"image": "<http://ghcr.io/org/app:tag|ghcr.io/org/app:tag>",
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.ghcr-prod }}"
}
Your 500 on /auth is consistent with missing/incorrect username or an invalid registry_url. Please try on the same host as the Docker worker:
docker login <http://ghcr.io|ghcr.io> -u <username> -p <token>
and use the exact same values in your DockerRegistryCredentials block.
Helpful references:
- Prefect Docker integration reference
- DockerRegistryCredentials block (source)
- Docker worker login code (source)
If you tell me which registry you’re using, I can provide the exact username/token/registry_url values you need.oleander-whirlwind
12/11/2025, 12:07 PMMarvin
12/11/2025, 12:07 PMMarvin
12/11/2025, 12:08 PMdocker login <artifactory> works but the Prefect Docker worker gets a 500 from /auth, this is usually a mismatch in the registry value or credentials format that Docker SDK is using versus what your CLI used.
Key things to check/fix for Artifactory
- Use the exact registry server address your CLI stores. After a successful CLI login, check your Docker config and copy the key used under auths:
cat ~/.docker/config.json
# Look under "auths": the key might be "<http://artifactory.myco.com|artifactory.myco.com>" or "<https://artifactory.myco.com>"
# Use that exact string for registry_url in your block
- No path segments in registry_url. For Artifactory, do NOT use /artifactory or a repo path in the registry_url. It should be just the host (and optional port), e.g.:
- OK: artifactory.myco.com
- OK: https://artifactory.myco.com
- Not OK: artifactory.myco.com/artifactory
- Image name must match the registry host you log into:
image: "<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>"
- Username is required. For Artifactory:
- username = your Artifactory username (or the token’s associated username)
- password = API key or access token
- If you’re using a custom cert/self-signed, make sure the Docker daemon trusts it. CLI success suggests this is already fine on that host.
How to set the block for Artifactory
from prefect_docker.credentials import DockerRegistryCredentials
creds = DockerRegistryCredentials(
username="my-artifactory-user",
password="<<access-token-or-api-key>>",
registry_url="<http://artifactory.myco.com|artifactory.myco.com>", # EXACTLY as in ~/.docker/config.json
reauth=True,
)
await creds.save("artifactory-prod", overwrite=True)
Then in your deployment
job_variables={
"image": "<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>",
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.artifactory-prod }}"
}
or pass the loaded block object in Python:
creds = DockerRegistryCredentials.load("artifactory-prod")
my_flow.deploy(..., job_variables={"registry_credentials": creds})
Quick isolation test on the worker host
Run this on the same machine where your Docker worker runs:
python - <<'PY'
import docker
c = docker.from_env()
print("Logging in...")
c.login(username="my-artifactory-user",
password="<<access-token-or-api-key>>",
registry="<http://artifactory.myco.com|artifactory.myco.com>")
print("Pulling image...")
c.images.pull("<http://artifactory.myco.com/docker-virtual/hello-world:latest|artifactory.myco.com/docker-virtual/hello-world:latest>")
print("OK")
PY
If this fails, the issue is between Docker SDK and your registry settings (e.g., wrong serveraddress string). Use whatever key appears in ~/.docker/config.json under "auths" for the registry parameter until the script succeeds; then mirror that in your Prefect block.
If it still fails, please share:
- The exact registry_url in your block
- The image value in your deployment
- The “auths” key from ~/.docker/config.json after your successful docker login
- A few lines of Docker worker logs around “Logging into Docker registry...”
Helpful references
- Prefect Docker integration reference
- Docker worker login code (source)oleander-whirlwind
12/11/2025, 12:21 PMMarvin
12/11/2025, 12:21 PMMarvin
12/11/2025, 12:22 PMdocker login and docker pull work on the host but the Prefect Docker worker still gets a 500 on /auth, this usually comes down to how the worker is logging in vs. how your CLI supplies credentials.
Key points to check
- Is your Docker worker running:
- on the host as a process, or
- inside a container?
If it’s in a container, it won’t see your host’s ~/.docker/config.json unless you mount it or provide explicit credentials via a block.
- The worker calls docker_client.login(...) if registry_credentials is set. That login hits the Docker daemon’s /auth endpoint. Some registries/daemons reject certain combinations (e.g., registry URL value, reauth behavior).
- The registry URL must match exactly what works with the CLI. Check the key under auths in ~/.docker/config.json after docker login and use that exact string as registry_url (no repo paths).
- For Artifactory, don’t include path segments in registry_url (just host[:port]), and ensure image name uses that host as prefix.
Try these, in order
1) Run the Python Docker SDK login test on the SAME machine and environment as your worker
This isolates whether it’s Prefect or the Docker SDK/daemon interaction:
python - <<'PY'
import docker, os, json
c = docker.from_env()
print("DOCKER_HOST:", os.environ.get("DOCKER_HOST"))
print("Logging in...")
# Replace with your values
c.login(
username="my-artifactory-user",
password="<<token-or-api-key>>",
registry="<http://artifactory.myco.com|artifactory.myco.com>", # EXACTLY match the key in ~/.docker/config.json
reauth=True,
)
print("Pulling image...")
c.images.pull("<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>")
print("OK")
PY
- If this fails with the same 500, the issue is with the registry_url/credentials/reauth at the Docker API level.
- If it succeeds, the worker’s environment differs (HOME/DOCKER_CONFIG/DOCKER_HOST, or it’s running in a container).
2) Try disabling re-auth
Some setups (esp. Artifactory/proxies) error on explicit re-auth:
from prefect_docker.credentials import DockerRegistryCredentials
creds = DockerRegistryCredentials(
username="my-artifactory-user",
password="<<token-or-api-key>>",
registry_url="<http://artifactory.myco.com|artifactory.myco.com>",
reauth=False, # <-- try False
)
await creds.save("artifactory-prod", overwrite=True)
Use that block in your deployment.
3) Ensure the worker sees the same Docker credentials/config as your CLI
- If the worker runs in a container, mount your Docker config:
- Mount host ~/.docker to the worker container at /root/.docker (or set DOCKER_CONFIG to a mounted path)
- If the worker runs as a different OS user, it won’t see your ~/.docker/config.json. Either:
- provide registry_credentials via block (preferred), or
- set DOCKER_CONFIG env to the path containing a usable config.json
4) As a temporary workaround, avoid login/pull entirely
If the image is already present on the worker host:
- Pre-pull the image on the host: docker pull <http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>
- Set pull policy to skip pulls:
job_variables={
"image": "<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>",
"image_pull_policy": "Never" # or "IfNotPresent" if you want it to pull only when missing
}
- Remove registry_credentials so the worker doesn’t attempt docker login.
5) Double-check the exact values
- Block’s `registry_url`: must match the key under "auths" in ~/.docker/config.json (no extra paths; host and optional port only).
- Image: must be artifactory-host/.../repo/image:tag and match the same host.
- Username/token: for Artifactory, a username is still required; put the token in password.
If it still fails, could you share:
- How your Docker worker is running (host process vs container)
- The registry_url value in your DockerRegistryCredentials block
- The image string in your deployment
- The exact auths key from ~/.docker/config.json after docker login (just the key, not the creds)Marvin
12/11/2025, 12:22 PMoleander-whirlwind
12/11/2025, 12:37 PMMarvin
12/11/2025, 12:37 PMMarvin
12/11/2025, 12:38 PMdocker login and docker pull work on the host, the worker should be able to do the same.
There are two reliable paths that avoid the /auth 500:
Option A — Skip explicit login and use your Docker config
This mirrors what works with the CLI.
- Remove registry_credentials from your deployment and pool.
- Ensure the worker process can read a config.json that already contains your Artifactory cred under auths.
- If the worker runs as a host process:
- Make sure the worker user has ~/.docker/config.json populated (same one you used to login), or set DOCKER_CONFIG to point to the directory containing it.
- If the worker runs inside a container:
- Mount your host Docker config into the worker container, e.g. mount ~/.docker to /root/.docker, or set DOCKER_CONFIG inside the container to a mounted path.
- Keep image_pull_policy as Always or IfNotPresent so it pulls when needed.
Example job_variables without login:
job_variables={
"image": "<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>",
"image_pull_policy": "IfNotPresent"
}
This avoids the explicit docker login call that’s currently failing and lets the SDK/daemon use the existing creds from config.json during pull.
Option B — Keep registry_credentials but adjust to match what Docker expects
Artifactory is sensitive to the exact server address and re-auth behavior.
- Set registry_url to exactly match the key under "auths" in ~/.docker/config.json after a successful CLI login. Try both forms if needed:
- <http://artifactory.myco.com|artifactory.myco.com>
- <https://artifactory.myco.com>
- Do not include path segments like /artifactory.
- Set reauth=False (some setups 500 on explicit reauth):
from prefect_docker.credentials import DockerRegistryCredentials
creds = DockerRegistryCredentials(
username="your-artifactory-username",
password="<<api-key-or-access-token>>",
registry_url="<http://artifactory.myco.com|artifactory.myco.com>", # or <https://artifactory.myco.com> — match your config.json
reauth=False,
)
await creds.save("artifactory-prod", overwrite=True)
Then use either a block reference or load the block and pass it:
job_variables={
"image": "<http://artifactory.myco.com/docker-virtual/myapp:tag|artifactory.myco.com/docker-virtual/myapp:tag>",
"registry_credentials": "{{ prefect.blocks.docker-registry-credentials.artifactory-prod }}"
}
# or
job_variables={"registry_credentials": DockerRegistryCredentials.load("artifactory-prod")}
Why A often works better here
- The worker won’t call the Docker /auth endpoint at all — it just pulls and the daemon uses existing auth.
- It matches your manual success path and avoids subtle differences in how docker-py formats login requests.
If it still fails, can you share:
- How your worker is running (host process vs container)
- The exact registry_url in your block
- The image string you’re pulling
- The key shown under "auths" in ~/.docker/config.json after docker login (just the server address)
- Whether setting reauth=False changed anything
For reference:
- Prefect Docker integration reference
- Docker worker login code (the worker only calls login if registry_credentials is set)