Skip to content

Docker Proxy

NORA can act as a pull-through cache for upstream Docker registries. When an image is not found locally, NORA fetches it from the configured upstream, caches it, and serves it to the client. Subsequent pulls are served from cache.

docker pull nora:4000/library/nginx:latest
┌──────┐ cache hit ┌─────────┐
│ NORA │ ───────────────► │ storage │
└──┬───┘ └─────────┘
│ cache miss
┌────────────┐ fetch + async cache
│ upstream │ ──────────────────────► storage
│ (Docker Hub)│
└────────────┘
  1. Client requests an image from NORA
  2. NORA checks local storage — if found, returns immediately (cache hit)
  3. If not found, tries each configured upstream sequentially
  4. First successful response is returned to the client
  5. Image is cached asynchronously in the background (fire-and-forget)
  6. Next pull of the same image is served from cache

Two environment variables control the proxy behavior:

VariableDefaultDescription
NORA_DOCKER_UPSTREAMShttps://registry-1.docker.ioComma-separated list of upstream registries
NORA_DOCKER_PROXY_TIMEOUT60HTTP timeout for upstream requests (seconds)

Default — no configuration needed. NORA proxies to Docker Hub out of the box:

Terminal window
docker pull nora.internal:4000/library/nginx:latest

NORA tries upstreams in order. If Docker Hub is down or doesn’t have the image, it falls back to the next:

Terminal window
NORA_DOCKER_UPSTREAMS="https://registry-1.docker.io,https://ghcr.io,https://quay.io"
[docker]
proxy_timeout = 60
[[docker.upstreams]]
url = "https://registry-1.docker.io"
[[docker.upstreams]]
url = "https://ghcr.io"
services:
nora:
image: ghcr.io/getnora-io/nora:latest
ports:
- 4000:4000
volumes:
- nora-data:/data
environment:
- NORA_HOST=0.0.0.0
- NORA_DOCKER_UPSTREAMS=https://registry-1.docker.io,https://ghcr.io
- NORA_DOCKER_PROXY_TIMEOUT=30
restart: unless-stopped
volumes:
nora-data:
Terminal window
# Instead of: docker pull nginx:latest
docker pull nora.internal:4000/library/nginx:latest
# Instead of: docker pull redis:7-alpine
docker pull nora.internal:4000/library/redis:7-alpine
# Non-library images (user repos):
docker pull nora.internal:4000/grafana/grafana:latest

If GHCR is in your upstreams list and the image is not on Docker Hub:

Terminal window
docker pull nora.internal:4000/getnora-io/nora:latest

Configure Docker daemon to use NORA as a registry mirror. Edit /etc/docker/daemon.json:

{
"registry-mirrors": ["http://nora.internal:4000"]
}

Restart Docker:

Terminal window
sudo systemctl restart docker

Now all docker pull commands automatically go through NORA:

Terminal window
# This now pulls via NORA cache:
docker pull nginx:latest

NORA handles upstream authentication automatically:

  1. Sends unauthenticated request to upstream
  2. On 401 Unauthorized, extracts Www-Authenticate header
  3. Fetches a bearer token from the upstream’s auth service
  4. Retries with the token
  5. Tokens are cached for 5 minutes per registry/repository

No credentials needed for public images. Docker Hub, GHCR, and Quay all support anonymous pulls for public repositories.

Any Docker Registry v2 API-compatible registry works:

RegistryURL
Docker Hubhttps://registry-1.docker.io
GitHub Container Registryhttps://ghcr.io
Quay.iohttps://quay.io
GitLab Container Registryhttps://registry.gitlab.com
Google Artifact Registryhttps://REGION-docker.pkg.dev
AWS ECRhttps://ACCOUNT.dkr.ecr.REGION.amazonaws.com

NORA tracks cache performance via Prometheus metrics at /metrics:

  • nora_cache_hits_total — images served from local cache
  • nora_cache_misses_total — images fetched from upstream
  • nora_docker_downloads_total — total Docker pull operations

Cache hit rate is also displayed on the Web UI dashboard.

  • Pull only — proxy is read-only. Push goes directly to NORA’s local storage, not forwarded upstream.
  • Sequential fallback — upstreams are tried one by one, not in parallel. If the first upstream times out, latency adds up.
  • No private upstream auth — only anonymous and bearer token auth (OAuth2 flow) is supported. Basic auth credentials in the config are not yet wired.
  • Async caching — cache writes happen in the background. If NORA restarts during caching, the image won’t be cached and will be fetched again on next pull.