Self-hosted service
rpardini/docker-registry-proxy avatar
rpardini/docker-registry-proxy

docker-registry-proxy: caching Docker pulls and centralising registry credentials

An HTTPS Proxy for Docker providing centralized configuration and caching of any registry (quay.io, DockerHub, registry.k8s.io, ghcr.io)

1,193 stars187 forksShellApache-2.0

At a glance

What is it?
docker-registry-proxy is an nginx based intercepting proxy that sits between your Docker clients and any registry, caching the large layer downloads and optionally the manifests, and holding the registry credentials in one place instead of on every node. It suits a Kubernetes cluster or a CI fleet that keeps hitting Docker Hub pull rate limits, and it asks you to install a root certificate on every client, which is the part to think about first.
Who is it for?
Adopt docker-registry-proxy if your fleet keeps hitting Docker Hub rate limits or you want registry credentials configured in one place rather than as secrets on every node, because the caching and the centralised authentication are the two things it does well. Do not adopt it without reading the security note about the CA certificate, since the proxy is a man in the middle and the volume holding that certificate is described as security sensitive.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 114 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the proxy is

The one line summary in the README is a caching proxy for Docker that allows centralised management of multiple registries and their authentication, and that caches images from any registry.

The mechanism is described bluntly in the What section. Essentially it is a man in the middle, an intercepting proxy based on nginx, to which all Docker traffic is directed using the HTTPS_PROXY mechanism and injected CA root certificates.

That framing is unusual in a README and it is the most useful thing in it, because the design has a consequence that the reader should know before anything else. A Docker client is configured to trust a certificate authority that the proxy owns, and every registry request the client makes is decrypted, inspected and re-encrypted by the proxy. That is what makes centralised authentication possible, and it is also what makes the certificate directory the most sensitive part of the deployment.

The main feature is Docker layer and image caching, including layers served from object storage such as S3 or Google Storage. The potentially huge blob and layer requests are cached for bandwidth and time savings. The manifest requests, the pulls themselves, are cached optionally, and that option exists for a specific reason, which is Docker Hub rate limiting.

The proxy is Apache-2.0 licensed, the current stable release is 0.7.0 from 2026-07-01, and the last push to the repository was on the same day.

The certificate, and the part you have to think about

The usage section is a list of things to set up, and one of them is not a setting but an instruction that changes the trust model of every client.

You configure the Docker clients once, and then all configuration is done on the proxy, and for this to work it requires inserting a root CA certificate into the system trusted root certificates of the client. There is a script in the repository, create_ca_cert.sh, that produces the certificate, and the volume mapping /ca is where the proxy stores it across restarts, with the README marking it as important and security sensitive.

The consequence is straightforward. Anything on the client side that trusts the proxy's CA trusts the proxy for the certificates it issues, so the certificate generation and the storage of that CA are the security boundary of the whole system, and the README's warning should be read as a warning rather than a formality.

The upside of the design is that it is the only way to centralise Docker registry credentials at the network level. The configuration is done once, on the proxy, and the client nodes need nothing. In a Kubernetes environment the README says as much, calling the centralised management of registry credentials something that can in itself be the main feature.

The other two volumes and settings are more ordinary. The cache volume is mapped for up to the cache maximum size of cached images across all cached registries, 32 gigabytes by default. And the proxy is exposed to the network on port 3128.

One more setting is worth flagging because it is easy to miss. The ALLOW_PUSH variable bypasses the proxy when pushing, and it defaults to false, so if it is left at false, pushing will not work.

Registry credentials in one place

The credential story is the second reason to run this, and it is independent of the caching.

The README describes it as a bonus feature, and then immediately notes that it can in itself be the main feature, for example in Kubernetes environments. The point is that a client which trusts the proxy does not need credentials of its own. The proxy holds the registry authentication and applies it, so a cluster of nodes does not each need an image pull secret, and a rotation of a credential happens in one place.

That is a real operational saving, and it is the one the project responded to when Docker Hub changed its policies. The 0.6.5 release notes are about the second Docker Hub apocalypse, the announcement of new pull limits, and the resulting surge of interest in the project, which the author answered by updating all dependencies, adding a test matrix, merging pull requests including a long standing request to disable IPv6, and updating the documentation. The author also says explicitly that there is no intention of abandoning the project.

The rate limit problem itself is described in the section on avoiding Docker Hub pull rate limits with caching. The symptom is an error response from the daemon saying too many requests, and the README notes that many unknowing Kubernetes clusters will hit the limit and struggle to configure image pull secrets and image pull policy.

So the two features, caching and centralised credentials, solve the same underlying problem from two directions. The cache reduces the number of requests that leave the network at all, and the centralised credentials remove the need to distribute them to every node.

Manifest caching and the immutability warning

Caching the blobs is the safe half of the caching story, because a layer is content addressed and a stale copy is a correct copy. Caching the manifests is the interesting half, and it comes with a warning that is stated in bold in the README: enabling manifest caching, in its default configuration, effectively makes some tags immutable. Use with care.

The configuration is a two tier system, described in the Dockerfile with the environment variables explained.

dockerfile
ENV ENABLE_MANIFEST_CACHE="false"

ENV MANIFEST_CACHE_PRIMARY_REGEX="(stable|nightly|production|test)"
ENV MANIFEST_CACHE_PRIMARY_TIME="10m"

ENV MANIFEST_CACHE_SECONDARY_REGEX="(.*)(\d|\.)+(.*)(\d|\.)+(.*)(\d|\.)+"
ENV MANIFEST_CACHE_SECONDARY_TIME="60d"

ENV MANIFEST_CACHE_DEFAULT_TIME="1h"

The primary tier is for frequently used or abused tags, with a default of ten minutes, and the comment points out that people publishing to production using the latest tag will want to include that in the regex, while heavy pullers who are being rate limited but do not mind getting outdated manifests should increase the cache time there.

The secondary tier catches any tag with three digits or dots, in the hope of matching most explicitly versioned tags, and it caches for sixty days, which is also the cache time for the large binary blobs the manifests refer to. That last detail is why the warning is not hypothetical. If the manifest is cached for sixty days, the blob it points at is effectively frozen too, because the client will not even ask for the new one.

The default tier covers everything else, with one hour, and the comment notes that in the default configuration the latest tag and other frequently used tags get this value.

The environment variable names carry a regex fragment, and the README points at the entrypoint script to understand how they are used, since they are applied as an nginx location with PCRE syntax.

Running it, and the images to pull

The usage section is short, and it is a deployment checklist rather than a tutorial.

Run the proxy on a host that is close to the Docker clients, network wise, so high bandwidth and the same VPC, and expose port 3128 to the network. Map the cache volume and the CA volume, and set the environment variables.

The image story has a wrinkle that trips people up. The latest tag and the latest-debug tag are unstable, built from master and amd64 only, while production and stable is the 0.7.0 tag, which is multi-arch for amd64 and arm64. So a production deployment wants a version tag, and the floating latest tag will not give you an arm64 image.

The images are also published on the GitHub Container Registry, with the Docker Hub image at rpardini/docker-registry-proxy and the GitHub one at ghcr.io/rpardini/docker-registry-proxy, and since the 0.5.x series the two carry the same images. The README gives the reason that is useful when you are rate limited: if you are already hitting Docker Hub limits, you cannot pull the proxy from Docker Hub, and the GitHub registry gives you a way in.

There are also two documents in the repository for the desktop cases, Docker Desktop on Windows and Docker for Mac, which is a sensible inclusion because the certificate installation is the awkward part on those platforms.

The nginx fork, the mitmproxy debug build and the state of the project

The Dockerfile explains two things about the implementation that a reader would otherwise have to discover.

The first is the base image. The project starts from the author's own nginx fork, which includes the proxy-connect module from tEngine, and the source is linked. The proxy-connect module is what makes the CONNECT method work for tunnelled HTTPS, so the fork is not a convenience, it is the thing that lets the proxy see inside the TLS session at all.

The second is the debug build. There is a build argument that, when enabled, builds mitmproxy inside the image through pip, and the comment is refreshingly honest about the cost, that it is heavy, takes minutes to build, and creates a layer of more than 90 megabytes. The pinned versions are visible in the build, with MarkupSafe and mitmproxy pinned to specific versions. The mitmproxy is there to help with debugging, and the entrypoint script checks the installed version if it was built.

The rest of the image is deliberately thin. The Alpine packages that are always present are bash, the certificate bundle, coreutils and openssl, and the cache and CA directories are created at build time and exposed as volumes so the certificate survives a restart.

On the state of the project, the release notes are the interesting part. The 0.7.0 release in July 2026 picked up an updated nginx and Alpine to fix what the note calls a gazillion CVEs, and merged a few pull requests, and the 0.6.5 release in February 2025 was the response to the Docker Hub policy change. The project is version 0.7.0, so the numbering has not yet reached one, and the image is a shell script and an nginx configuration on top of a custom nginx build, which is a shape that a security reviewer can follow.

Editorial conclusion

Adopt docker-registry-proxy if your fleet keeps hitting Docker Hub rate limits or you want registry credentials configured in one place rather than as secrets on every node, because the caching and the centralised authentication are the two things it does well. Do not adopt it without reading the security note about the CA certificate, since the proxy is a man in the middle and the volume holding that certificate is described as security sensitive. Before you enable manifest caching, decide which tags you can afford to treat as immutable, because the default configuration makes some of them exactly that.

Frequently asked questions

What does docker-registry-proxy do?

It is an nginx based intercepting proxy for Docker that caches image layers and blobs, optionally caches manifests, and centralises the credentials used to pull from any registry. Docker clients are pointed at it through the HTTPS_PROXY mechanism with an injected root CA certificate, and all configuration then happens on the proxy.

How do I avoid Docker Hub pull rate limits with docker-registry-proxy?

Set the ENABLE_MANIFEST_CACHE environment variable to true, which caches the manifest requests that Docker Hub throttles, and fine tune the other parameters. The README warns that in the default configuration this effectively makes some tags immutable, so you should tighten the regular expressions or widen the primary tier if that affects you.

What volumes and environment variables does the proxy need?

A volume for the cached images at /docker_mirror_cache, bounded by CACHE_MAX_SIZE which defaults to 32 gigabytes, and a volume for /ca where the proxy stores its CA certificate, which the README marks as security sensitive. The proxy is exposed on port 3128, and ALLOW_PUSH must be set if you want pushes to work, since it defaults to false.

Which image should I run in production?

The 0.7.0 tag, which is multi-arch for amd64 and arm64. The latest and latest-debug tags are unstable, built from master and amd64 only. The images are published both on Docker Hub and on the GitHub Container Registry, and the GitHub one is useful if you are already rate limited and cannot pull the proxy from Docker Hub.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. rpardini/docker-registry-proxy on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rpardini-docker-registry-proxy.svg)](https://hysenlabs.com/projects/rpardini-docker-registry-proxy)

Was this useful?