Production Deployment Guide
Overview
Section titled “Overview”This guide covers deploying NORA in production environments with HTTPS/TLS support using reverse proxies.
Reverse Proxy Setup
Section titled “Reverse Proxy Setup”Option 1: Caddy (Recommended)
Section titled “Option 1: Caddy (Recommended)”Caddy provides automatic HTTPS with Let’s Encrypt and simple configuration.
Caddyfile:
nora.example.com { reverse_proxy localhost:4000 { header_up Host {host} header_up X-Real-IP {remote} header_up X-Forwarded-For {remote} header_up X-Forwarded-Proto {scheme} }}With custom certificate:
{ auto_https disable_redirects}
nora.example.com:443 { tls /etc/ssl/certs/nora.crt /etc/ssl/private/nora.key
reverse_proxy localhost:4000 { header_up Host {host} header_up X-Real-IP {remote} }
log { output file /var/log/caddy/nora-access.log }}Start Caddy:
caddy run --config /etc/caddy/CaddyfileOption 2: Nginx
Section titled “Option 2: Nginx”nginx.conf:
upstream nora_backend { server localhost:4000;}
server { listen 443 ssl http2; server_name nora.example.com;
ssl_certificate /etc/ssl/certs/nora.crt; ssl_certificate_key /etc/ssl/private/nora.key;
# SSL hardening ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
# Proxy settings location / { proxy_pass http://nora_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for large image uploads proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; }
# API endpoint location /api/ { proxy_pass http://localhost:4000/; }
# Metrics endpoint location /metrics { proxy_pass http://localhost:4000/metrics; allow 10.0.0.0/8; # Restrict to internal network deny all; }}
# HTTP to HTTPS redirectserver { listen 80; server_name nora.example.com; return 301 https://$host$request_uri;}Option 3: Traefik
Section titled “Option 3: Traefik”docker-compose.yml with Traefik:
version: '3.8'
services: nora: image: ghcr.io/getnora-io/nora:latest restart: unless-stopped volumes: - nora-data:/data environment: NORA_STORAGE_PATH: /data labels: - "traefik.enable=true" - "traefik.http.routers.nora.rule=Host(`nora.example.com`)" - "traefik.http.routers.nora.entrypoints=websecure" - "traefik.http.routers.nora.tls.certresolver=letsencrypt" - "traefik.http.services.nora.loadbalancer.server.port=4000"
traefik: image: traefik:v2.10 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock - traefik-certs:/letsencrypt command: - "--providers.docker=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
volumes: nora-data: traefik-certs:Docker Compose Production Setup
Section titled “Docker Compose Production Setup”Full production stack with Caddy:
version: '3.8'
services: nora: image: ghcr.io/getnora-io/nora:latest container_name: nora restart: unless-stopped ports: - "127.0.0.1:4000:4000" # NORA (Registry + API + UI) volumes: - nora-data:/data environment: # Storage NORA_STORAGE_PATH: /data
# Rate Limits (tuned for production) NORA_RATE_LIMIT_UPLOAD_RPS: 2000 NORA_RATE_LIMIT_UPLOAD_BURST: 5000 NORA_RATE_LIMIT_GENERAL_RPS: 1000 NORA_RATE_LIMIT_GENERAL_BURST: 2000
# Logging NORA_LOG_LEVEL: info NORA_LOG_FORMAT: json
# Metrics NORA_METRICS_ENABLED: "true" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:4000/health"] interval: 30s timeout: 10s retries: 3
caddy: image: caddy:2-alpine container_name: caddy restart: unless-stopped ports: - "443:443" - "80:80" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - ./certs:/etc/ssl/certs:ro - ./private:/etc/ssl/private:ro - caddy-data:/data - caddy-config:/config depends_on: - nora
volumes: nora-data: driver: local caddy-data: caddy-config:Certificate Management
Section titled “Certificate Management”Let’s Encrypt (Automatic)
Section titled “Let’s Encrypt (Automatic)”Caddy handles this automatically. Just use hostname in Caddyfile:
nora.example.com { reverse_proxy localhost:4000}Custom CA (FreeIPA, Internal PKI)
Section titled “Custom CA (FreeIPA, Internal PKI)”1. Generate certificate request:
openssl req -new -newkey rsa:2048 -nodes \ -keyout nora.key \ -out nora.csr \ -subj "/CN=nora.example.com/O=MyOrg/C=US"2. Sign with your CA and install:
# Copy signed certificate and keycp nora.crt /etc/ssl/certs/cp nora.key /etc/ssl/private/chmod 600 /etc/ssl/private/nora.key
# Add CA certificate to system trust storecp ca.crt /usr/local/share/ca-certificates/update-ca-certificates3. Configure reverse proxy with custom cert (see examples above)
Client Configuration
Section titled “Client Configuration”Docker
Section titled “Docker”On client machines, add CA certificate:
# Linuxsudo cp ca.crt /usr/local/share/ca-certificates/sudo update-ca-certificatessudo systemctl restart docker
# macOSsudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain ca.crtVerify access:
docker login nora.example.comdocker pull nora.example.com/myapp:latestKubernetes
Section titled “Kubernetes”Add CA to all nodes:
# On each nodesudo cp ca.crt /usr/local/share/ca-certificates/nora-ca.crtsudo update-ca-certificatessudo systemctl restart containerdUpdate image references in manifests:
containers:- name: myapp image: nora.example.com/myapp:latestSystemd Service
Section titled “Systemd Service”Create /etc/systemd/system/nora.service:
[Unit]Description=NORA Container RegistryAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=noraGroup=noraWorkingDirectory=/opt/noraExecStart=/usr/local/bin/nora serve \ --storage-path /var/lib/nora \ --rate-limit-upload-rps 2000 \ --rate-limit-upload-burst 5000Restart=on-failureRestartSec=10
# Security hardeningNoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/nora
[Install]WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reloadsudo systemctl enable norasudo systemctl start norasudo systemctl status noraMonitoring
Section titled “Monitoring”Health check endpoint:
curl https://nora.example.com/health# Expected: {"status":"ok"}Metrics endpoint (Prometheus):
curl http://localhost:4000/metricsSee Monitoring Guide for Prometheus/Grafana setup.
Performance Tuning
Section titled “Performance Tuning”Rate Limits
Section titled “Rate Limits”For heavy CI/CD environments, increase rate limits:
NORA_RATE_LIMIT_UPLOAD_RPS=2000 # Default: 200NORA_RATE_LIMIT_UPLOAD_BURST=5000 # Default: 500NORA_RATE_LIMIT_GENERAL_RPS=1000 # Default: 500NORA_RATE_LIMIT_GENERAL_BURST=2000 # Default: 1000Monitor /metrics for nora_rate_limit_hits_total to tune appropriately.
Storage
Section titled “Storage”Local storage:
- Use fast SSD for
/data - Regular cleanup of old tags
- Monitor disk usage
S3-compatible storage:
NORA_STORAGE_TYPE=s3NORA_S3_BUCKET=nora-registryNORA_S3_REGION=us-east-1NORA_S3_ENDPOINT=https://s3.amazonaws.comSecurity Considerations
Section titled “Security Considerations”- Always use HTTPS in production - Docker/containerd require secure registries
- Restrict metrics endpoint - Use firewall or reverse proxy rules
- Enable authentication - See Configuration/Authentication
- Keep CA certificates updated on all client machines
- Monitor for unauthorized access via logs and metrics
- Regular backups of
/datadirectory
Troubleshooting
Section titled “Troubleshooting”Problem: “x509: certificate signed by unknown authority”
Solution: Install CA certificate on client machines (see Client Configuration above)
Problem: Rate limit errors
Solution: Increase rate limits via environment variables and restart NORA
Problem: Slow uploads
Solution: Check reverse proxy timeouts, increase if needed