easy-docker-proxy app icon
Open source · No layer cache · Self-hosted · Docker deploy

easy-docker-proxyEDP

A multi-upstream Docker registry proxy — one domain for all of them.

Streaming pull-through: no image layer cache, and no Docker socket. Docker Hub keeps its original image names, other registries use a path prefix on your domain, and a pull stats console is built in.

MIT licensed · Data plane and admin plane are separate

EDP stats console dashboard: pull trends, upstream share, error rate and active clients

Stats console (demo data): pull trends, upstream share and error rate at a glance.

A lightweight registry proxy

It does one thing: forward pull requests to the right upstream registry. No hoarding images, no managing containers.

WhatA multi-upstream Docker Registry proxy on your own server and your own domain
CacheNo image layer cache, streaming pull-through; only SQLite metadata (pull records) on disk
EntryOne domain: Docker Hub goes through registry-mirrors, other upstreams use a path prefix
AdminBuilt-in stats web UI; no Docker socket, no container management
StackWritten in Go, deployed with Docker Compose, MIT licensed

Light, low-maintenance, observable

Streaming, no layer cache

Layers are relayed as they download and never written to disk, so there is no cache to size or clean up.

One domain, many upstreams

Docker Hub keeps its image names through registry-mirrors; ghcr.io, quay.io and others add a path prefix after your domain.

Nine upstreams out of the box

Docker Hub, GHCR, Quay, GCR, registry.k8s.io, MCR, NVCR, Elastic and public ECR. Turn any off, or add your own.

Pull stats console

A dashboard, per-upstream analytics, an image list and an event log, in light and dark themes.

Optional upstream credentials

Public images are pulled anonymously by default. Add a username and token per upstream to pull private images or avoid Docker Hub rate limits.

Client login

Turn on pull_auth so only clients that ran docker login against the proxy can pull, and strangers cannot use it.

Separate data and admin planes

The data plane listens on 127.0.0.1:5000 by default and is published through a TLS reverse proxy; the admin plane on :5001 is not exposed publicly by default.

No Docker socket

It never mounts docker.sock and manages no containers, so the permission boundary is easy to see.

Free and open source

MIT-licensed, with English and Chinese READMEs, an install guide and troubleshooting docs on GitHub.

Supported upstreams

Add the matching path prefix after your domain. The proxy picks the upstream from it and strips the prefix before forwarding.

UpstreamPath prefixPull example
Docker Hubdocker.io, or mirrors (no prefix)docker pull nginx
GHCRghcr.iodocker pull reg.example.com/ghcr.io/owner/app:tag
Quay.ioquay.iodocker pull reg.example.com/quay.io/org/img:tag
GCRgcr.iodocker pull reg.example.com/gcr.io/project/img:tag
Kubernetesregistry.k8s.io、k8s.gcr.iodocker pull reg.example.com/registry.k8s.io/pause:3.9
Microsoft MCRmcr.microsoft.comdocker pull reg.example.com/mcr.microsoft.com/...
NVIDIA NGCnvcr.iodocker pull reg.example.com/nvcr.io/...
Elasticdocker.elastic.codocker pull reg.example.com/docker.elastic.co/...
AWS Public ECRpublic.ecr.awsdocker pull reg.example.com/public.ecr.aws/...

Note: reg.example.com in the table is only an example; use your own domain. Upstreams are pulled anonymously by default; to pull private images or avoid rate limits, put the matching username and token in .env. Set enabled to false to turn off an upstream you do not need.

Three steps

Deploy the proxy

Clone the repo, fill in the environment variables and start it with one Docker Compose command.

Set up the domain and HTTPS

Point the domain at the proxy host, reverse-proxy it to 127.0.0.1:5000 with Caddy or Nginx, and put the same domain in config.yaml.

Update the clients

For Docker Hub, add one mirror line to daemon.json; for other registries, put your domain in front of the image name.

Client usage

Replace every reg.example.com below with your own domain.

Docker Hub: registry-mirrors

Image names stay exactly the same. Put this JSON in /etc/docker/daemon.json:

{
  "registry-mirrors": ["https://reg.example.com"]
}
systemctl restart docker   # Linux
docker pull nginx:alpine

If the entry point is plain HTTP, also add it to insecure-registries.

Other upstreams: path prefix

Add the upstream's prefix after the same domain:

docker pull reg.example.com/ghcr.io/owner/app:tag
docker pull reg.example.com/quay.io/prometheus/prometheus:latest
docker pull reg.example.com/registry.k8s.io/pause:3.9

The proxy picks the upstream by prefix and strips it before calling the real registry.

Do not hijack official names like ghcr.io to the proxy via /etc/hosts; the certificate will not match and TLS will fail.

Quick deploy

You need a 64-bit Linux host (or Docker Desktop locally), Docker with Docker Compose v2, and outbound HTTPS to the upstream registries.

  1. Get the code

    Clone the repo and enter the directory; run the remaining commands from there.

    git clone https://github.com/AlexWuYh/easy-docker-proxy.git
    cd easy-docker-proxy
  2. Prepare the environment variables

    Copy the template, then generate a random admin token and stats password into .env. The stats login defaults to admin, and its password is PROXY_WEB_PASSWORD.

    cp .env.example .env
    
    # Generate a random admin token and stats password
    EDP_ADMIN_TOKEN="$(openssl rand -hex 32)"
    EDP_WEB_PASSWORD="$(openssl rand -base64 24 | tr -d '\n')"
    
    # Write them into .env
    sed -i.bak -e "s|^PROXY_ADMIN_TOKEN=.*|PROXY_ADMIN_TOKEN=$EDP_ADMIN_TOKEN|" \
      -e "s|^PROXY_WEB_PASSWORD=.*|PROXY_WEB_PASSWORD=$EDP_WEB_PASSWORD|" .env
    rm -f .env.bak
    
    # Clear the plaintext variables from this shell
    unset EDP_ADMIN_TOKEN EDP_WEB_PASSWORD
  3. Put in your domain

    Copy the config, then change hosts of dockerhub to your own domain (without https://).

    cp configs/config.docker.yaml configs/config.yaml
    registries:
      - name: dockerhub
        hosts:
          - "reg.example.com"   # ← replace with your real domain
        path_prefixes:
          - "docker.io"
  4. Start it

    Run it from the repo root; the first run builds the image.

    docker compose --env-file .env -f deploy/docker-compose.yaml up -d --build
Data planeListens on host 127.0.0.1:5000 by default; publish it through a TLS reverse proxy
Admin plane:5001, not exposed publicly by default
HTTPSPrefer your existing Caddy proxying to 127.0.0.1:5000
Stats UIMap 127.0.0.1:5001 and open /stats/login.html

Bring your own domain: reg.example.com in the docs is only an example. Register a domain, point A / AAAA (or CNAME) at the proxy host, then put the real hostname in Caddy / Nginx and in hosts of config.yaml. Until DNS takes effect, client pulls will fail.

Full install, configuration and troubleshooting notes (currently in Chinese): install guide, troubleshooting.

Stats console

Who pulls what, and which upstream fails — all in one page. Screenshots use demo data; click for full size.

Upstream analytics
Upstream analytics: pulls and traffic per upstream
Image list
Images: which images are pulled the most
Event log
Events: every pull and error, one by one
Login page
Login: the stats page requires an account

Speed up image pulls in minutes

Free, open source, and running on your own server.