My private Docker Registry is publicly accessible. What should I do?
A Docker Registry with no authentication lets anyone list and pull your private images — and a container image is a perfect copy of your application: source code, configuration, and any secret baked in at build time. A writable registry is worse still: an attacker can push a poisoned image you’ll happily deploy. Here’s how to close it and contain the fallout.
First: don’t panic — block the registry, then assume the images were pulled.
Pulling an image is quiet and quick; you may never see it in logs. Close access immediately, then treat everything inside those images — code and secrets — as public and rotate accordingly.
// the 60-second version
- Firewall port 5000 / put the registry behind authentication now.
- Assume every image was pulled — rotate all secrets baked into them.
- Check whether it was writable and audit for pushed/poisoned images.
- Re-issue clean images with secrets removed from the layers.
01Understand what’s at risk
A container image isn’t a black box — it’s an extractable filesystem with build history. An open registry hands an attacker:
- Your source code — pull an image, unpack the layers, read the app.
- Baked-in secrets — API keys, DB passwords, private keys and tokens added during build (via
ARG/ENVor copied files) sit in the layers, even if “removed” later. - Your architecture — base images, dependencies and versions reveal what to attack next.
- A supply-chain foothold — if pushes are allowed, an attacker can overwrite a tag with a backdoored image.
Image history is like git history — deleting a file in a later layer doesn’t remove it. Any secret that ever entered a layer must be considered leaked and rotated, not just the ones in the final image.
02Stop the bleeding — require auth / block the port
Make the registry unreachable to the public: firewall port 5000, then put it behind authentication and TLS.
ufw deny 5000
REGISTRY_AUTH=htpasswd REGISTRY_AUTH_HTPASSWD_REALM="Registry" REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
Confirm an unauthenticated request now fails — curl https://registry:5000/v2/_catalog should return 401, not a repo list.
03Rotate every secret in the images
This is the real cleanup. Inventory what the images contained and rotate all of it:
- Pull each image and inspect its layers (
docker history,dive) for embedded secrets. - Rotate every credential found — DB passwords, API keys, signing keys, cloud tokens — at the source.
- Rebuild images with build secrets / multi-stage builds so nothing sensitive lands in a layer.
# layer-by-layer history docker history --no-trunc <image> # what the open registry listed curl -s https://registry:5000/v2/_catalog
04Check for tampering — was it writable?
Determine whether the registry only leaked (read) or was also writable (push). A writable open registry is a supply-chain emergency.
- Check registry/proxy logs for
PUT/POSTto/v2/…/blobsor/manifests— those are pushes. - Compare image digests against what your CI built; a mismatch means a tag was overwritten.
- If you can’t rule out a poisoned image, rebuild from source and redeploy known-good digests.
05Read the logs — who pulled?
Pulls are GETs to manifests and blobs. Review your reverse-proxy/registry logs for unfamiliar IPs and for the catalog endpoint being hit — a classic recon move.
grep -E "/v2/|_catalog" /var/log/nginx/access.log
- Unknown IPs enumerating
/v2/_catalog= recon; treat every listed repo as pulled. - Note the timestamps to define your exposure window.
06Make sure it can’t happen again
- Never expose a registry without auth + TLS; keep it on a private network or VPN.
- Keep secrets out of images — use build secrets, runtime env, or a secrets manager.
- Scan images in CI for embedded secrets and vulnerabilities.
- Sign images (cosign) and verify signatures on deploy.
- Monitor for catalog enumeration and unexpected pushes.
Was this guide useful?
These playbooks are free to read and share. If a heads-up ever saved you a bad week, you can say thanks — or jump into the other guides.