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; the image is pulled straight from GHCR.

Set up the domain and HTTPS

Point the domain at the proxy host, put it in PROXY_DOMAIN in .env, then reverse-proxy to 127.0.0.1:5000 with Caddy or Nginx.

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, generate a random admin token and stats password, and put your ingress domain in PROXY_DOMAIN (without https://). 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 (replace PROXY_DOMAIN with your real domain)
    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|" \
      -e "s|^PROXY_DOMAIN=.*|PROXY_DOMAIN=your-real-domain|" .env
    rm -f .env.bak
    
    # Clear the plaintext variables from this shell
    unset EDP_ADMIN_TOKEN EDP_WEB_PASSWORD

    .env does not run $(...), so generate the random values in the shell first and then write them in. If the data-plane port is taken, also set PROXY_PORT (default 5000); to pin an image version, set PROXY_IMAGE.

  3. Prepare the config

    Just copy the template. The dockerhub hosts entry is already written as ${PROXY_DOMAIN} and expands from the domain in .env, so there is no YAML to edit.

    cp configs/config.docker.yaml configs/config.yaml
    registries:
      - name: dockerhub
        hosts:
          - "${PROXY_DOMAIN:-reg.example.com}"   # taken from PROXY_DOMAIN in .env
        path_prefixes:
          - "docker.io"
  4. Start it

    Run it from the repo root. By default it pulls the multi-arch image published by CI, ghcr.io/alexwuyh/easy-docker-proxy:latest (amd64 / arm64); to build from local source instead, append --build.

    docker compose --env-file .env -f deploy/docker-compose.yaml up -d
  5. Verify

    Hit the data plane with your ingress domain as the Host header; a 200 means the proxy is ready.

    set -a; . ./.env; set +a
    curl -sS -o /dev/null -w '%{http_code}\n' \
      -H "Host: $PROXY_DOMAIN" "http://127.0.0.1:${PROXY_PORT:-5000}/v2/"

    To upgrade to a newer image later:

    docker compose --env-file .env -f deploy/docker-compose.yaml pull
    docker compose --env-file .env -f deploy/docker-compose.yaml up -d
Data planeListens on host 127.0.0.1:5000 by default (change with PROXY_PORT); 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 (match PROXY_PORT); without one, add --profile edge to use the bundled Caddy, whose site name comes from PROXY_DOMAIN
Stats UIMap 127.0.0.1:5001 and just open it in a browser

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 PROXY_DOMAIN in .env and in Caddy / Nginx. 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.