Pulling self-hosted images from a private registry
Mirror self-hosted worker images into a private registry and configure Docker or Kubernetes workers to pull from it.
Mirror the images used by self-hosted workers when compute hosts cannot pull directly from a public registry. Your registry becomes the pull source for the worker process, task environment, Warp Agent sidecar, and Kubernetes preflight Job.
Images to mirror
Inventory the images used by each worker pool before blocking public-registry egress.
| Image | Purpose | Configuration |
|---|---|---|
warpdotdev/oz-agent-worker | Long-lived worker daemon | Docker run image or Helm image.repository |
| Environment or default image | Main task filesystem and toolchain | Warp environment image or Kubernetes defaultImage |
warpdotdev/warp-agent:latest | Warp Agent runtime mounted at /agent | sidecar_image or Helm kubernetesBackend.sidecarImage |
busybox:1.36 | Kubernetes startup preflight | preflight_image or Helm kubernetesBackend.preflightImage |
Pin the worker image to the immutable timestamp tag or digest from the worker release. The supported self-hosted sidecar reference is warpdotdev/warp-agent:latest, listed on the Warp Agent tags page. Refresh the mirror regularly. To pin the sidecar, set its private-registry override to a mirrored digest and update that digest when Warp publishes a new sidecar.
These steps cover Warp Agent runs without Computer Use. Computer Use and third-party harnesses use additional images. Contact your Warp account team before enabling them in a worker pool that blocks public-registry access.
Copying images into the registry
Use a registry copy tool that preserves all image architectures. The following example uses Skopeo. Replace WORKER_RELEASE_TAG with the immutable tag from the worker release.
export PRIVATE_REGISTRY="registry.internal.example.com/warp"
skopeo login registry.internal.example.com
skopeo copy --all \
"docker://docker.io/warpdotdev/oz-agent-worker:WORKER_RELEASE_TAG" \
"docker://${PRIVATE_REGISTRY}/oz-agent-worker:WORKER_RELEASE_TAG"
skopeo copy --all \
"docker://docker.io/warpdotdev/warp-agent:latest" \
"docker://${PRIVATE_REGISTRY}/warp-agent:latest"
skopeo copy --all \
"docker://docker.io/library/busybox:1.36" \
"docker://${PRIVATE_REGISTRY}/busybox:1.36"
skopeo copy --all \
"docker://docker.io/library/ubuntu:22.04" \
"docker://${PRIVATE_REGISTRY}/agent-base:22.04"The example mirrors Ubuntu as the task image. Replace that source with your own task image, then set the private image reference on the Warp environment used by the worker pool.
Configuring the Docker backend
The Docker configuration below requires worker release v2026-08-28-21-43-14 or newer.
The Docker backend uses the worker's Docker config to authenticate task-image pulls. Warp Agent sidecar pulls do not use those credentials.
If the registry requires authentication, follow Private Docker registries to create and mount a dedicated Docker config for a containerized worker. Because sidecar pulls do not use credentials, pre-pull every task and sidecar image before setting the pull policy to Never:
export WORKER_DOCKER_CONFIG="/var/lib/oz-agent-worker/docker-config"
sudo docker --config "$WORKER_DOCKER_CONFIG" \
pull registry.internal.example.com/warp/agent-base:22.04
sudo docker --config "$WORKER_DOCKER_CONFIG" \
pull registry.internal.example.com/warp/warp-agent:latestIf the worker runs as a host process with credentials in your default Docker config, run the same commands without sudo or --config "$WORKER_DOCKER_CONFIG".
Before starting a containerized worker, run docker login registry.internal.example.com on the host so Docker can pull the mirrored worker image.
Set the pull policy to Never so the worker uses the local images:
worker_id: "private-registry-docker"
backend:
docker:
image_pull_policy: "Never"
sidecar_image: "registry.internal.example.com/warp/warp-agent:latest"For a sidecar repository that allows anonymous reads, use image_pull_policy: "Always" instead. The sidecar uses the mutable latest tag. The pull policy applies to task and sidecar images.
With Never, local images do not update automatically. After refreshing the mirrored warp-agent:latest, repeat the sidecar pre-pull command on every worker host before routing another run.
The task image must point to the private registry through its Warp environment; the worker does not rewrite task image registry names.
In the containerized worker command, use registry.internal.example.com/warp/oz-agent-worker:WORKER_RELEASE_TAG in place of the public worker image. Add --volume "$PWD/worker.yaml:/etc/oz-agent-worker/worker.yaml:ro" before the image and --config-file /etc/oz-agent-worker/worker.yaml after it. See the config file reference.
Start the worker with --log-level debug, then route a test run to the worker. Before blocking public-registry egress, confirm that the worker logs for Using Docker image and Preparing additional sidecar show private-registry references and the run succeeds.
Configuring the Kubernetes backend
Export a self-hosted worker API key as WARP_API_KEY, then create the namespace, API key Secret, and registry pull secret:
export WARP_API_KEY="YOUR_API_KEY"
kubectl create namespace warp-oz \
--dry-run=client \
--output yaml | kubectl apply --filename -
kubectl create secret generic oz-agent-worker \
--namespace warp-oz \
--from-literal=WARP_API_KEY="$WARP_API_KEY"
export REGISTRY_AUTH_DIR="$(mktemp -d)"
skopeo login \
--compat-auth-file "$REGISTRY_AUTH_DIR/config.json" \
registry.internal.example.com
kubectl create secret generic warp-registry \
--namespace warp-oz \
--type=kubernetes.io/dockerconfigjson \
--from-file=.dockerconfigjson="$REGISTRY_AUTH_DIR/config.json"After the Secret is created, remove the temporary auth file:
rm -r "$REGISTRY_AUTH_DIR"
unset REGISTRY_AUTH_DIRSet the worker, task, sidecar, and preflight image references in a Helm values file:
warp:
apiKeySecret:
name: oz-agent-worker
image:
repository: registry.internal.example.com/warp/oz-agent-worker
tag: WORKER_RELEASE_TAG
pullPolicy: IfNotPresent
pullSecrets:
- name: warp-registry
kubernetesBackend:
defaultImage: registry.internal.example.com/warp/agent-base:22.04
imagePullPolicy: Always
preflightImage: registry.internal.example.com/warp/busybox:1.36
sidecarImage: registry.internal.example.com/warp/warp-agent:latest
podTemplate:
imagePullSecrets:
- name: warp-registryWhen a run uses a Warp environment image, that image takes precedence over defaultImage. Set the private image reference on the Warp environment before blocking public-registry egress.
Before installing the worker, verify the preflight image with the same pull secret configured in kubernetesBackend.podTemplate:
kubectl delete job warp-preflight-image-check \
--namespace warp-oz \
--ignore-not-found
kubectl apply --filename - <<'EOF'
apiVersion: batch/v1
kind: Job
metadata:
name: warp-preflight-image-check
namespace: warp-oz
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
imagePullSecrets:
- name: warp-registry
containers:
- name: check
image: registry.internal.example.com/warp/busybox:1.36
imagePullPolicy: Always
command: ["/bin/sh", "-c", "true"]
EOF
kubectl wait \
--namespace warp-oz \
--for=condition=complete \
--timeout=2m \
job/warp-preflight-image-checkThe wait command must report condition met. After it completes, remove the temporary Job:
kubectl delete job warp-preflight-image-check --namespace warp-ozFrom a network with GitHub access, clone the worker release and package its chart:
git clone \
--branch "WORKER_RELEASE_TAG" \
--depth 1 \
https://github.com/warpdotdev/oz-agent-worker.git
tar --create --gzip \
--file "oz-agent-worker-chart-WORKER_RELEASE_TAG.tar.gz" \
--directory oz-agent-worker/charts \
oz-agent-workerCopy the archive to your approved internal artifact store. On the deployment host, extract it to an internal path:
mkdir --parents /srv/warp/charts
tar --extract --gzip \
--file "oz-agent-worker-chart-WORKER_RELEASE_TAG.tar.gz" \
--directory /srv/warp/chartsOn the deployment host, set WORKER_CHART to the internal chart copy and install it with temporary verification settings:
export WORKER_CHART="/srv/warp/charts/oz-agent-worker"
helm upgrade --install oz-agent-worker "$WORKER_CHART" \
--namespace warp-oz \
--set worker.workerId=private-registry-kubernetes \
--set worker.logLevel=debug \
--set worker.cleanup=false \
--values private-registry-values.yamlimage.pullSecrets authenticates the long-lived worker Deployment. kubernetesBackend.podTemplate.imagePullSecrets authenticates task Jobs and the startup preflight Job.
Verifying Kubernetes registry isolation
Follow the worker logs until they show Successfully connected to server, then press Ctrl+C:
kubectl logs --follow deployment/oz-agent-worker --namespace warp-ozA successful test run verifies the registry credentials by pulling and running the task and sidecar images.
With the Oz CLI (see Installing the CLI), start one run that uses the Warp Agent without Computer Use:
oz agent run-cloud \
--host "private-registry-kubernetes" \
--prompt "Print the operating system release and exit."Cleanup is temporarily disabled, so the successful task Job and Pod remain after the command returns. Confirm the debug logs show the selected task and sidecar images:
kubectl logs deployment/oz-agent-worker --namespace warp-oz |
grep -E 'Using Kubernetes task image|Overriding server sidecar image'Select the newest Job for this worker, derive its Pod name, and inspect every image reference:
TASK_JOB="$(kubectl get jobs \
--namespace warp-oz \
--selector oz-worker-id=private-registry-kubernetes \
--sort-by=.metadata.creationTimestamp \
--output name | tail -n 1)"
TASK_POD="$(kubectl get pods \
--namespace warp-oz \
--selector "job-name=${TASK_JOB#*/}" \
--output jsonpath='{.items[0].metadata.name}')"
kubectl get pod "$TASK_POD" --namespace warp-oz \
--output jsonpath='{range .spec.initContainers[*]}{.image}{"\n"}{end}{range .spec.containers[*]}{.image}{"\n"}{end}'For this Kubernetes worker, block public-registry egress only after the preflight image check completes, the worker connects, the test run succeeds, and every task Pod image uses the private registry. Restore the default log level and cleanup behavior, then remove the retained test Job:
helm upgrade oz-agent-worker "$WORKER_CHART" \
--namespace warp-oz \
--set worker.workerId=private-registry-kubernetes \
--set worker.logLevel=info \
--set worker.cleanup=true \
--values private-registry-values.yaml
kubectl delete "$TASK_JOB" --namespace warp-ozTroubleshooting
Docker reports pull access denied
For a task-image failure, confirm the worker's Docker config contains registry credentials. For a sidecar failure, allow anonymous reads from the mirrored sidecar repository, or pre-pull the image and use image_pull_policy: "Never".
Kubernetes reports ImagePullBackOff
Confirm warp-registry exists in the task namespace. The worker Deployment needs image.pullSecrets, while task and preflight Pods need podTemplate.imagePullSecrets.
Related pages
- Managed: Docker backend — Configure Docker daemon and private-registry access.
- Managed: Kubernetes backend — Configure the Helm chart, task Pod template, and preflight job.
- Environments — Set the task image used by self-hosted runs.
- Security and networking — Review image egress and other network requirements.