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_PROXIES | https://registry-1.docker.io | Comma-separated list of upstream registries |
Note:
NORA_DOCKER_UPSTREAMSis deprecated and works as a backward-compatible alias. UseNORA_DOCKER_PROXIES.
| 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_PROXIES="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_PROXIES=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”Public registries (no config needed)
Section titled “Public registries (no config needed)”For public images, NORA handles authentication automatically:
- Sends 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
Docker Hub, GHCR, and Quay all support anonymous pulls for public repositories.
Private registries (Basic Auth)
Section titled “Private registries (Basic Auth)”For private upstream registries that require credentials, use the url|user:pass format:
NORA_DOCKER_PROXIES="https://registry.corp.com|admin:secret,https://registry-1.docker.io"Or in config.toml:
[[docker.upstreams]]url = "https://registry.corp.com"auth = "admin:secret"
[[docker.upstreams]]url = "https://registry-1.docker.io"When credentials are configured, NORA sends Basic Auth on the initial request (preemptive auth) and also uses them for bearer token acquisition.
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.
- 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