Kubernetes Deployment Examples
This directory contains production-ready Kubernetes manifests for deploying NORA.
Quick Start
Section titled “Quick Start”# Deploy with default configurationkubectl apply -k .
# Verify deploymentkubectl get pods -n nora-systemkubectl get svc -n nora-systemnamespace.yaml- Namespace definitionpvc.yaml- PersistentVolumeClaim for storagedeployment.yaml- NORA deploymentservice.yaml- Service (ClusterIP)ingress-nginx.yaml- Ingress for nginxingress-contour.yaml- HTTPProxy for Contourconfigmap.yaml- Configuration (optional)secret.yaml- Secrets (optional)kustomization.yaml- Kustomize overlay
namespace.yaml
Section titled “namespace.yaml”apiVersion: v1kind: Namespacemetadata: name: nora-system labels: name: nora-systempvc.yaml
Section titled “pvc.yaml”apiVersion: v1kind: PersistentVolumeClaimmetadata: name: nora-storage namespace: nora-systemspec: accessModes: - ReadWriteOnce resources: requests: storage: 100Gi storageClassName: fast-ssd # Adjust to your storage classdeployment.yaml
Section titled “deployment.yaml”apiVersion: apps/v1kind: Deploymentmetadata: name: nora namespace: nora-system labels: app: noraspec: replicas: 1 # NORA does not support multi-replica yet strategy: type: Recreate # Required for RWO volumes selector: matchLabels: app: nora template: metadata: labels: app: nora spec: containers: - name: nora image: ghcr.io/getnora-io/nora:latest imagePullPolicy: Always ports: - name: http containerPort: 4000 protocol: TCP env: # Storage - name: NORA_STORAGE_PATH value: /data
# Rate Limits (production values) - name: NORA_RATE_LIMIT_UPLOAD_RPS value: "2000" - name: NORA_RATE_LIMIT_UPLOAD_BURST value: "5000" - name: NORA_RATE_LIMIT_GENERAL_RPS value: "1000" - name: NORA_RATE_LIMIT_GENERAL_BURST value: "2000"
# Logging - name: NORA_LOG_LEVEL value: info - name: NORA_LOG_FORMAT value: json
# Metrics - name: NORA_METRICS_ENABLED value: "true"
volumeMounts: - name: storage mountPath: /data
resources: requests: cpu: 500m memory: 256Mi limits: cpu: 2000m memory: 1Gi
livenessProbe: httpGet: path: /health port: 4000 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5
readinessProbe: httpGet: path: /health port: 4000 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3
securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: false capabilities: drop: - ALL
volumes: - name: storage persistentVolumeClaim: claimName: nora-storage
# Optional: Specify node affinity for SSD nodes # affinity: # nodeAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # nodeSelectorTerms: # - matchExpressions: # - key: storage-type # operator: In # values: # - ssdservice.yaml
Section titled “service.yaml”apiVersion: v1kind: Servicemetadata: name: nora namespace: nora-system labels: app: noraspec: type: ClusterIP ports: port: 4000 targetPort: 4000 - name: http port: 4000 targetPort: 4000 protocol: TCP selector: app: noraingress-nginx.yaml
Section titled “ingress-nginx.yaml”For nginx Ingress Controller:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: nora namespace: nora-system annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/proxy-body-size: "0" # Unlimited upload size nginx.ingress.kubernetes.io/proxy-read-timeout: "600" nginx.ingress.kubernetes.io/proxy-send-timeout: "600"spec: ingressClassName: nginx tls: - hosts: - nora.example.com secretName: nora-tls rules: - host: nora.example.com http: paths: # Docker Registry API - path: /v2 pathType: Prefix backend: service: name: nora port: number: 4000 # NORA API/UI - path: /api pathType: Prefix backend: service: name: nora port: number: 4000 # Root (registry) - path: / pathType: Prefix backend: service: name: nora port: number: 4000ingress-contour.yaml
Section titled “ingress-contour.yaml”For Contour Ingress Controller (HTTPProxy):
apiVersion: projectcontour.io/v1kind: HTTPProxymetadata: name: nora namespace: nora-systemspec: virtualhost: fqdn: nora.example.com tls: secretName: nora-tls routes: # Docker Registry API - conditions: - prefix: /v2 services: - name: nora port: 4000 timeoutPolicy: response: 10m # Large uploads need long timeout
# NORA API - conditions: - prefix: /api services: - name: nora port: 4000
# Metrics (restricted) - conditions: - prefix: /metrics services: - name: nora port: 4000 # Optional: IP allowlist # ipAllowPolicy: # - cidr: 10.0.0.0/8
# Default route (registry) - conditions: - prefix: / services: - name: nora port: 4000configmap.yaml (Optional)
Section titled “configmap.yaml (Optional)”For YAML-based configuration:
apiVersion: v1kind: ConfigMapmetadata: name: nora-config namespace: nora-systemdata: config.yaml: | storage: type: local path: /data
rate_limits: upload: rps: 2000 burst: 5000 general: rps: 1000 burst: 2000
logging: level: info format: json
metrics: enabled: trueMount in deployment:
volumes:- name: config configMap: name: nora-configvolumeMounts:- name: config mountPath: /etc/nora/config.yaml subPath: config.yamlsecret.yaml (Optional)
Section titled “secret.yaml (Optional)”For authentication credentials:
apiVersion: v1kind: Secretmetadata: name: nora-auth namespace: nora-systemtype: OpaquestringData: username: admin password: changemeReference in deployment:
env:- name: NORA_AUTH_TYPE value: basic- name: NORA_AUTH_USERNAME valueFrom: secretKeyRef: name: nora-auth key: username- name: NORA_AUTH_PASSWORD valueFrom: secretKeyRef: name: nora-auth key: passwordkustomization.yaml
Section titled “kustomization.yaml”apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
namespace: nora-system
resources:- namespace.yaml- pvc.yaml- deployment.yaml- service.yaml- ingress-nginx.yaml # Or ingress-contour.yaml
# Optional# - configmap.yaml# - secret.yaml
commonLabels: app.kubernetes.io/name: nora app.kubernetes.io/component: registry
images:- name: ghcr.io/getnora-io/nora newTag: latest # Pin to specific version in productionDeployment
Section titled “Deployment”Using kubectl
Section titled “Using kubectl”# Create namespacekubectl apply -f namespace.yaml
# Deploy storagekubectl apply -f pvc.yaml
# Deploy NORAkubectl apply -f deployment.yamlkubectl apply -f service.yaml
# Deploy ingress (choose one)kubectl apply -f ingress-nginx.yaml# ORkubectl apply -f ingress-contour.yaml
# Wait for rolloutkubectl rollout status deployment/nora -n nora-systemUsing Kustomize
Section titled “Using Kustomize”# Deploy everythingkubectl apply -k .
# Verifykubectl get all -n nora-systemUsing Helm (if available)
Section titled “Using Helm (if available)”helm install nora . -n nora-system --create-namespaceVerification
Section titled “Verification”# Check pod statuskubectl get pods -n nora-system
# Check logskubectl logs -n nora-system -l app=nora -f
# Check servicekubectl get svc -n nora-system
# Test from inside clusterkubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl http://nora.nora-system.svc.cluster.local:5000/v2/
# Test from outside (after ingress)curl https://nora.example.com/v2/Client Configuration
Section titled “Client Configuration”Docker Login
Section titled “Docker Login”docker login nora.example.comdocker tag myapp:latest nora.example.com/myapp:latestdocker push nora.example.com/myapp:latestKubernetes ImagePullSecrets
Section titled “Kubernetes ImagePullSecrets”# Create secretkubectl create secret docker-registry nora-registry \ --docker-server=nora.example.com \ --docker-username=admin \ --docker-password=changeme \ -n default
# Use in podspec: imagePullSecrets: - name: nora-registry containers: - name: myapp image: nora.example.com/myapp:latestStorage Considerations
Section titled “Storage Considerations”Local Storage
Section titled “Local Storage”Pros:
- Fast
- Simple
Cons:
- Not scalable
- Pod tied to specific node
Best for: Single-node, development
NFS/NAS
Section titled “NFS/NAS”Pros:
- Shared across nodes
- Easy backup
Cons:
- Network latency
- Single point of failure
Best for: Small clusters, on-premise
Cloud Storage (S3-compatible)
Section titled “Cloud Storage (S3-compatible)”Pros:
- Highly available
- Unlimited capacity
- No PVC needed
Cons:
- Network dependency
- Cost
Configuration:
env:- name: NORA_STORAGE_TYPE value: s3- name: NORA_S3_BUCKET value: nora-registry- name: NORA_S3_REGION value: us-east-1- name: NORA_S3_ENDPOINT value: https://s3.amazonaws.com- name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: s3-creds key: access-key-id- name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: s3-creds key: secret-access-keyBest for: Production, multi-region
Monitoring
Section titled “Monitoring”Prometheus ServiceMonitor
Section titled “Prometheus ServiceMonitor”apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: nora namespace: nora-systemspec: selector: matchLabels: app: nora endpoints: - port: api path: /metrics interval: 30sGrafana Dashboard
Section titled “Grafana Dashboard”See Monitoring Guide for dashboard JSON.
Troubleshooting
Section titled “Troubleshooting”Problem: Pod stuck in Pending
kubectl describe pod -n nora-system -l app=noraCheck PVC status and node affinity.
Problem: CrashLoopBackOff
kubectl logs -n nora-system -l app=nora --previousCheck volume permissions and configuration.
Problem: 404 on /v2/
Check Ingress configuration and service endpoints:
kubectl get endpoints -n nora-systemkubectl describe ingress nora -n nora-systemSecurity Hardening
Section titled “Security Hardening”- Enable authentication (basic or OIDC)
- Use NetworkPolicies to restrict access
- Enable Pod Security Standards
- Rotate credentials regularly
- Scan NORA image for vulnerabilities
- Use read-only root filesystem where possible
- Limit resource usage with ResourceQuotas