When you’re building container images from a private branch or working on security fixes that shouldn’t be pushed to a public registry, the OpenShift internal image registry is a practical alternative. The images stay on your cluster, no external registry account is needed, and you avoid leaking unreleased code to public repositories.
This post walks through the full workflow: enabling external access to the registry, authenticating, building images locally, pushing them, and referencing them in deployments. It also covers the gotchas I hit along the way.
1. Enable the Default Route
By default, the OpenShift internal registry is not exposed externally. You need to enable the default route so you can push images from your local machine.
oc patch configs.imageregistry.operator.openshift.io/cluster \
--type merge \
--patch '{"spec":{"defaultRoute":true}}'
Then get the registry hostname:
REGISTRY=$(oc get route default-route -n openshift-image-registry -o jsonpath='{.spec.host}')
echo $REGISTRY
# Example: default-route-openshift-image-registry.apps.mycluster.example.com
2. Authentication
Grant Push Permissions
Your OpenShift user needs the registry-editor cluster role to push images:
oc adm policy add-cluster-role-to-user registry-editor $(oc whoami)
Without this, pushes will fail with denied even if your user has cluster-admin for everything else. The internal registry has its own RBAC that’s separate from the general cluster roles.
Log In to the Registry
Use your OpenShift token to authenticate:
docker login $REGISTRY -u $(oc whoami) -p $(oc whoami -t)
The token is short-lived. If your pushes start failing with unauthorized, re-run the login command to get a fresh token.
Create the Target Namespace
The namespace you’re pushing to must exist before you push. The registry won’t auto-create it:
oc new-project my-images
# or
oc create namespace my-images
3. Build and Push
The Straightforward Way (docker push)
If you’re building natively (same architecture as your target), you can build and push directly:
docker build -t $REGISTRY/my-images/my-app:dev .
docker push $REGISTRY/my-images/my-app:dev
Cross-Architecture Builds (Apple Silicon → amd64)
If you’re building on Apple Silicon for an amd64 cluster, you’ll likely use docker buildx. Here’s where it gets tricky.
Build locally with --load:
docker buildx build --platform linux/amd64 \
-t $REGISTRY/my-images/my-app:dev \
--load .
Don’t use --push with buildx. In my experience, docker buildx --push to the OpenShift internal registry returns HTTP 500 errors on manifest upload.
Push with skopeo via docker-archive instead:
# Save the image as a tarball
docker save $REGISTRY/my-images/my-app:dev -o /tmp/my-app.tar
# Push via skopeo
skopeo copy \
--override-arch amd64 --override-os linux \
--dest-creds="$(oc whoami):$(oc whoami -t)" \
docker-archive:/tmp/my-app.tar \
docker://$REGISTRY/my-images/my-app:dev
# Clean up
rm /tmp/my-app.tar
This two-step approach (save + skopeo copy) is reliable and works consistently.
Pushing Multiple Images
For pushing several images at once, a simple loop does the job:
CREDS="$(oc whoami):$(oc whoami -t)"
for img in api-server driver launcher; do
docker save "$REGISTRY/my-images/${img}:dev" -o "/tmp/${img}.tar"
skopeo copy --override-arch amd64 --override-os linux \
--dest-creds="$CREDS" \
"docker-archive:/tmp/${img}.tar" \
"docker://$REGISTRY/my-images/${img}:dev"
rm "/tmp/${img}.tar"
done
4. Using Images in Deployments
Once pushed, images are available inside the cluster via the internal service URL:
image-registry.openshift-image-registry.svc:5000/<namespace>/<image>:<tag>
For example, in a Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-project
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: image-registry.openshift-image-registry.svc:5000/my-images/my-app:dev
You can verify the images are in the registry by listing image streams:
oc get is -n my-images
5. Gotchas
Docker Buildx --push Returns HTTP 500
docker buildx bake --push (and docker buildx build --push) fails against the OpenShift internal registry with unexpected status 500 on manifest upload. The workaround is to build with --load, save as a tarball, and push with skopeo copy docker-archive:.
Skopeo Can’t Find Docker Daemon (macOS/Colima)
If you’re using Colima on macOS, skopeo copy docker-daemon: fails because skopeo looks for /var/run/docker.sock, not Colima’s socket at ~/.colima/default/docker.sock. Skopeo ignores the DOCKER_HOST environment variable for the docker-daemon: transport.
The workaround is to use docker save + docker-archive: transport instead of docker-daemon:.
