Parvez Mohammed
10/22/2025, 7:24 AMMarvin
10/22/2025, 7:24 AMMarvin
10/22/2025, 7:37 AMDockerImage packager itself does not accept registry credentials. Pushing to a private registry relies on the Docker daemon’s auth (i.e., you must be logged in) or you can use the prefect_docker deployment steps with explicit credentials.
Two supported approaches:
1) Prefect YAML with explicit credentials (best for CI and private registries)
- Install extras and register blocks:
uv run --with 'prefect[docker]' prefect block register -m prefect_docker
- Option A: Use a DockerRegistryCredentials block
# Create the block (run once)
from pydantic import SecretStr
from prefect_docker.credentials import DockerRegistryCredentials
DockerRegistryCredentials(
username="myuser",
password=SecretStr("my-token-or-password"),
registry_url="<http://ghcr.io|ghcr.io>" # or your registry
).save("ghcr-creds", overwrite=True)
- Reference it in prefect.yaml
# prefect.yaml
deployments:
- name: my-deploy
entrypoint: flows/flow.py:flow
build:
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://ghcr.io/myorg/myimage|ghcr.io/myorg/myimage>
tag: "{{ flow_run.id }}"
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://ghcr.io/myorg/myimage|ghcr.io/myorg/myimage>
tag: "{{ flow_run.id }}"
# Provide credentials via a block reference or env vars (see Option B)
credentials: "{{ blocks.docker_registry_credentials/ghcr-creds }}"
- Option B: Use environment variables (no block needed)
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://ghcr.io/myorg/myimage|ghcr.io/myorg/myimage>
tag: "{{ flow_run.id }}"
credentials:
registry_url: "<http://ghcr.io|ghcr.io>"
username: "{{ env.DOCKER_USERNAME }}"
password: "{{ env.DOCKER_PASSWORD }}"
Helpful docs:
- prefect.yaml deployments
- Docker packaging with Prefect
2) Python deploy() with DockerImage (use docker login on the runner)
When using flow.from_source(...).deploy(image=DockerImage(...), build=True, push=True), Prefect uses your local Docker daemon. Make sure the environment running deploy is logged into the registry first.
Example:
from prefect import flow
from prefect.packaging.docker import DockerImage
@flow
def my_flow():
...
my_flow.from_source("<https://github.com/myorg/myrepo>", entrypoint="flows/flow.py:my_flow").deploy(
name="prod",
work_pool_name="docker-pool",
image=DockerImage(name="<http://ghcr.io/myorg/myimage|ghcr.io/myorg/myimage>", tag="latest"),
build=True,
push=True,
)
- In CI (GitHub Actions):
- uses: docker/login-action@v3
with:
registry: <http://ghcr.io|ghcr.io>
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: uv run --with 'prefect[docker]' python deploy.py
- For AWS ECR:
aws ecr get-login-password --region $AWS_REGION \
| docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$<http://AWS_REGION.amazonaws.com|AWS_REGION.amazonaws.com>
Key points:
- DockerImage does not take credentials directly; pushing uses Docker’s auth.
- For YAML-based deployments, you can explicitly pass credentials to prefect_docker.deployments.steps.push_docker_image.
- For Python .deploy(), make sure the machine running deploy has already performed docker login (or equivalent action) to your registry.
If you can share which registry you’re using (Docker Hub, GHCR, ECR, GCR, etc.) and whether you prefer YAML or Python-based deployments, I can tailor the exact snippet for you.Parvez Mohammed
10/22/2025, 8:25 AMMarvin
10/22/2025, 8:30 AMMarvin
10/22/2025, 8:31 AM.deploy().
Here’s a complete example you can run locally or in CI:
- Install extras
pip install "prefect[docker]"
- Set env vars (use a Docker Hub access token, not your password)
export DOCKERHUB_USERNAME=your-username
export DOCKERHUB_TOKEN=your-token
- deploy.py
import os
import subprocess
from prefect import flow
from prefect.packaging.docker import DockerImage
def dockerhub_login():
username = os.environ["DOCKERHUB_USERNAME"]
token = os.environ["DOCKERHUB_TOKEN"]
# Login to the default registry (Docker Hub)
subprocess.run(
["docker", "login", "-u", username, "--password-stdin"],
input=token.encode(),
check=True,
)
@flow
def my_flow():
print("Hello from Prefect + Docker!")
if __name__ == "__main__":
dockerhub_login()
my_flow.from_source(
"<https://github.com/your-org/your-repo>",
entrypoint="flows/flow.py:my_flow",
).deploy(
name="prod",
work_pool_name="docker-pool", # adjust to your pool
image=DockerImage(
name="<http://docker.io/your-username/your-image|docker.io/your-username/your-image>", # or "your-username/your-image"
tag="latest",
# Optional: build arguments, labels, platforms, etc.
# build-arg=... (pass as keyword args if needed)
),
build=True,
push=True,
)
In CI (GitHub Actions) you can rely on the Docker login action instead of the Python login step:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: pip install "prefect[docker]"
- run: python deploy.py
Key points:
- DockerImage does not accept registry credentials; pushing uses Docker’s auth.
- For Docker Hub, logging in with docker login (or the login action in CI) before calling .deploy(build=True, push=True) is the correct approach.
- Use a Docker Hub access token for security.
Docs for reference:
- Docker image packaging with Prefect
- Deploy flows from source
If you want, share your repo/image names and I’ll tailor the snippet exactly to your setup.Parvez Mohammed
10/22/2025, 11:52 AMMarvin
10/22/2025, 11:52 AMMarvin
10/22/2025, 11:56 AMdeployments:
- name: my-deploy
entrypoint: flows/flow.py:flow
build:
- prefect.deployments.steps.run_shell_script:
id: get_version
# Print the version without a trailing newline
script: |
python - <<'PY'
import sys
import soc
sys.stdout.write(getattr(soc, "__version__", "0.0.0"))
PY
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
# Optional: reuse in job variables (e.g., worker image) or deployment tags
# tags:
# - "soc-{{ get_version.stdout }}"
Notes:
- The script uses sys.stdout.write to avoid a trailing newline, which makes for a clean Docker tag.
- You can reference the same {{ get_version.stdout }} anywhere templating is supported (e.g., additional_tags, job_variables.image, etc.).
If you prefer to set the tag in Python (since you mentioned you use from soc import __version__):
from prefect import flow
from prefect.packaging.docker import DockerImage
import soc
@flow
def my_flow():
...
my_flow.from_source(
"<https://github.com/your-org/your-repo>",
entrypoint="flows/flow.py:my_flow",
).deploy(
name="prod",
work_pool_name="docker-pool",
image=DockerImage(
name="<http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>",
tag=soc.__version__,
),
build=True,
push=True,
)
Docs for reference:
- prefect.yaml templating and steps
- Docker image packaging with Prefect
If you meant Prefect deployment metadata tags (not Docker tags), say the word and I’ll show that pattern too.Parvez Mohammed
10/22/2025, 12:05 PMMarvin
10/22/2025, 12:05 PMMarvin
10/22/2025, 12:06 PMdeployments:
- name: my-deploy
entrypoint: flows/flow.py:flow
build:
- prefect.deployments.steps.run_shell_script:
id: get_version
script: |
# Option A: parse soc/__init__.py
if [ -f soc/__init__.py ]; then
v=$(sed -nE "s/^__version__ *= *<[^'\"]+|'\">['\"].*$/\1/p" soc/__init__.py | head -n1)
printf "%s" "$v"
exit 0
fi
# Option B: parse pyproject.toml
if [ -f pyproject.toml ]; then
v=$(sed -nE 's/^version *= *"([^"]+)".*$/\1/p' pyproject.toml | head -n1)
printf "%s" "$v"
exit 0
fi
# Option C: read version.txt
if [ -f version.txt ]; then
tr -d '\n' < version.txt
exit 0
fi
echo "0.0.0" # fallback
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
# optionally:
# additional_tags:
# - "soc-{{ get_version.stdout }}"
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
Notes:
- The sed expressions are portable (-E works on macOS and Linux). They extract the first version match and print it without a trailing newline.
- You can reuse the same value anywhere templating is supported, e.g., additional_tags, job_variables.image, etc.
If you tell me exactly where your version lives (init file path, pyproject, or a version file), I’ll tailor the script down to a single, minimal line.
Docs:
- prefect.yaml templating and steps
- Docker image packaging with PrefectParvez Mohammed
10/22/2025, 12:58 PMMarvin
10/22/2025, 12:58 PMMarvin
10/22/2025, 1:01 PM{{ prefect.variables.VARIABLE_NAME }}
How to set and use:
1) Create/set a variable
prefect variable set DOCKER_TAG v1.2.3
# optional: overwrite if it already exists
prefect variable set DOCKER_TAG v1.2.4 --overwrite
2) Use it in prefect.yaml
- For Docker image tags in build/push steps:
deployments:
- name: my-deploy
entrypoint: flows/flow.py:flow
build:
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ prefect.variables.DOCKER_TAG }}"
push:
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ prefect.variables.DOCKER_TAG }}"
- For job variables (e.g., environment passed to the worker/container):
job_variables:
env:
DOCKER_TAG: "{{ prefect.variables.DOCKER_TAG }}"
APP_ENV: "{{ prefect.variables.APP_ENV }}"
Where templating works:
- build, push, and pull sections (pull resolves at runtime)
- job_variables (e.g., image, env)
- within step parameters (and cross-step references via {{ step_id.field }})
Notes:
- Variables are strings; set them to the exact value you want used.
- If a variable is missing, deployment may fail at template resolution time.
- For sensitive values, consider blocks (e.g., Secrets) instead of variables.
Docs:
- Prefect Variables
- prefect.yaml templatingParvez Mohammed
10/22/2025, 5:48 PMMarvin
10/22/2025, 5:48 PMMarvin
10/22/2025, 5:49 PM- prefect.deployments.steps.run_shell_script:
id: get_version
directory: "."
script: |
set -euxo pipefail
# bail out after 30s if something goes wrong
timeout 30s bash -c '
v=""
if [ -f soc/__init__.py ]; then
v=$(awk -F"[\x22\x27]" '"'"'/__version__/{print $2; exit}'"'"' soc/__init__.py)
elif [ -f pyproject.toml ]; then
v=$(awk -F\" '"'"'/^version *=/{print $2; exit}'"'"' pyproject.toml)
elif [ -f version.txt ]; then
v=$(tr -d "\n" < version.txt)
fi
if [ -z "$v" ]; then
echo "Could not determine version" >&2
exit 1
fi
printf "%s" "$v"
'
Then reference it:
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_version.stdout }}"
If you were using a Python heredoc like python - <<'PY', the most common cause of “stuck” is the heredoc terminator not matching exactly (PY) or getting mis-indented so the shell keeps waiting for the end marker.
Prefer to avoid the shell step entirely?
- Set a Prefect Variable in CI or locally, then use it in YAML. This removes the run_shell_script step:
prefect variable set APP_VERSION 1.2.3
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ prefect.variables.APP_VERSION }}"
Extra diagnostics:
- Run the script locally (outside Prefect) to ensure it returns quickly.
- Enable more logs when deploying: set PREFECT_LOGGING_LEVEL=DEBUG in your environment.
- If your script references files, confirm the working directory is what you expect (use the directory: parameter or insert pwd && ls -la at the top for troubleshooting).
If you paste your current run_shell_script step and the last few log lines from prefect deploy, I can identify the exact cause and give a targeted fix.Parvez Mohammed
10/22/2025, 5:51 PMMarvin
10/22/2025, 5:51 PMMarvin
10/22/2025, 5:52 PM<< (not the HTML << you pasted here).
- No logs: you’ve set stream_output: false, so you won’t see what’s happening even if it’s stuck.
Easiest fix: avoid heredocs entirely and use python -c. Also turn on output and add a timeout.
Try this:
- prefect.deployments.steps.run_shell_script:
id: get_soc_version
directory: "."
stream_output: true
script: |
set -euxo pipefail
# Use python3 if that's your default; otherwise change to `python`
timeout 30s python3 -c 'import sys, soc; sys.stdout.write(getattr(soc, "__version__", "0.0.0"))'
Then reference it as before:
- prefect_docker.deployments.steps.build_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_soc_version.stdout }}"
- prefect_docker.deployments.steps.push_docker_image:
image_name: <http://docker.io/youruser/yourimage|docker.io/youruser/yourimage>
tag: "{{ get_soc_version.stdout }}"
If you prefer to keep the heredoc, make sure the terminator is exact and not indented. For example:
script: |
set -euxo pipefail
timeout 30s python3 - <<'PY'
import sys
import soc
sys.stdout.write(getattr(soc, "__version__", "0.0.0"))
PY
If this still hangs, it usually means the shell didn’t see the terminating PY exactly. The python -c approach avoids this entirely.
Extra tips:
- Ensure the soc package is importable at deploy time (run from repo root or set PYTHONPATH).
- Use stream_output: true while debugging.
- You can eliminate the shell step entirely by setting a Prefect Variable (e.g., prefect variable set APP_VERSION 1.2.3) and referencing {{ prefect.variables.APP_VERSION }}.
Docs:
- prefect.yaml templating and steps
If it still hangs after this change, paste the new logs (with stream_output: true) and I’ll pinpoint the exact cause.