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.
How it Works
Section titled “How it Works”docker pull nora:4000/library/nginx:latest │ ▼ ┌──────┐ cache hit ┌─────────┐ │ NORA │ ───────────────► │ storage │ └──┬───┘ └─────────┘ │ cache miss ▼ ┌────────────┐ fetch + async cache │ upstream │ ──────────────────────► storage │ (Docker Hub)│ └────────────┘- Client requests an image from NORA
- NORA checks local storage — if found, returns immediately (cache hit)
- If not found, tries each configured upstream sequentially
- First successful response is returned to the client
- Image is cached asynchronously in the background (fire-and-forget)
- Next pull of the same image is served from cache
Configuration
Section titled “Configuration”Two environment variables control the proxy behavior:
| Variable | Default | Description |
|---|---|---|
NORA_DOCKER_UPSTREAMS | https://registry-1.docker.io | Comma-separated list of upstream registries |
NORA_DOCKER_PROXY_TIMEOUT | 60 | HTTP timeout for upstream requests (seconds) |
Single upstream (Docker Hub)
Section titled “Single upstream (Docker Hub)”Default — no configuration needed. NORA proxies to Docker Hub out of the box:
docker pull nora.internal:4000/library/nginx:latestMultiple upstreams with fallback
Section titled “Multiple upstreams with fallback”NORA tries upstreams in order. If Docker Hub is down or doesn’t have the image, it falls back to the next:
NORA_DOCKER_UPSTREAMS="https://registry-1.docker.io,https://ghcr.io,https://quay.io"config.toml
Section titled “config.toml”[docker]proxy_timeout = 60
[[docker.upstreams]]url = "https://registry-1.docker.io"
[[docker.upstreams]]url = "https://ghcr.io"Docker Compose Example
Section titled “Docker Compose Example”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:Usage Examples
Section titled “Usage Examples”Pull from Docker Hub via NORA
Section titled “Pull from Docker Hub via NORA”# Instead of: docker pull nginx:latestdocker pull nora.internal:4000/library/nginx:latest
# Instead of: docker pull redis:7-alpinedocker pull nora.internal:4000/library/redis:7-alpine
# Non-library images (user repos):docker pull nora.internal:4000/grafana/grafana:latestPull from GHCR via NORA
Section titled “Pull from GHCR via NORA”If GHCR is in your upstreams list and the image is not on Docker Hub:
docker pull nora.internal:4000/getnora-io/nora:latestUse as a Docker mirror
Section titled “Use as a Docker mirror”Configure Docker daemon to use NORA as a registry mirror. Edit /etc/docker/daemon.json:
{ "registry-mirrors": ["http://nora.internal:4000"]}Restart Docker:
sudo systemctl restart dockerNow all docker pull commands automatically go through NORA:
# This now pulls via NORA cache:docker pull nginx:latestAuthentication
Section titled “Authentication”NORA handles upstream authentication automatically:
- Sends unauthenticated request to upstream
- On
401 Unauthorized, extractsWww-Authenticateheader - Fetches a bearer token from the upstream’s auth service
- Retries with the token
- 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.
Supported Upstreams
Section titled “Supported Upstreams”Any Docker Registry v2 API-compatible registry works:
| Registry | URL |
|---|---|
| Docker Hub | https://registry-1.docker.io |
| GitHub Container Registry | https://ghcr.io |
| Quay.io | https://quay.io |
| GitLab Container Registry | https://registry.gitlab.com |
| Google Artifact Registry | https://REGION-docker.pkg.dev |
| AWS ECR | https://ACCOUNT.dkr.ecr.REGION.amazonaws.com |
Monitoring
Section titled “Monitoring”NORA tracks cache performance via Prometheus metrics at /metrics:
nora_cache_hits_total— images served from local cachenora_cache_misses_total— images fetched from upstreamnora_docker_downloads_total— total Docker pull operations
Cache hit rate is also displayed on the Web UI dashboard.
Limitations
Section titled “Limitations”- 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.
See Also
Section titled “See Also”- Settings — all configuration options
- TLS / HTTPS — reverse proxy setup
- Production Guide — deployment best practices