Skip to content

Kubernetes Deployment Examples

This directory contains production-ready Kubernetes manifests for deploying NORA.

Terminal window
# Deploy with default configuration
kubectl apply -k .
# Verify deployment
kubectl get pods -n nora-system
kubectl get svc -n nora-system

  • namespace.yaml - Namespace definition
  • pvc.yaml - PersistentVolumeClaim for storage
  • deployment.yaml - NORA deployment
  • service.yaml - Service (ClusterIP)
  • ingress-nginx.yaml - Ingress for nginx
  • ingress-contour.yaml - HTTPProxy for Contour
  • configmap.yaml - Configuration (optional)
  • secret.yaml - Secrets (optional)
  • kustomization.yaml - Kustomize overlay

apiVersion: v1
kind: Namespace
metadata:
name: nora-system
labels:
name: nora-system

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nora-storage
namespace: nora-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: fast-ssd # Adjust to your storage class

apiVersion: apps/v1
kind: Deployment
metadata:
name: nora
namespace: nora-system
labels:
app: nora
spec:
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:
# - ssd

apiVersion: v1
kind: Service
metadata:
name: nora
namespace: nora-system
labels:
app: nora
spec:
type: ClusterIP
ports:
port: 4000
targetPort: 4000
- name: http
port: 4000
targetPort: 4000
protocol: TCP
selector:
app: nora

For nginx Ingress Controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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: 4000

For Contour Ingress Controller (HTTPProxy):

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: nora
namespace: nora-system
spec:
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: 4000

For YAML-based configuration:

apiVersion: v1
kind: ConfigMap
metadata:
name: nora-config
namespace: nora-system
data:
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: true

Mount in deployment:

volumes:
- name: config
configMap:
name: nora-config
volumeMounts:
- name: config
mountPath: /etc/nora/config.yaml
subPath: config.yaml

For authentication credentials:

apiVersion: v1
kind: Secret
metadata:
name: nora-auth
namespace: nora-system
type: Opaque
stringData:
username: admin
password: changeme

Reference 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: password

apiVersion: kustomize.config.k8s.io/v1beta1
kind: 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 production

Terminal window
# Create namespace
kubectl apply -f namespace.yaml
# Deploy storage
kubectl apply -f pvc.yaml
# Deploy NORA
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# Deploy ingress (choose one)
kubectl apply -f ingress-nginx.yaml
# OR
kubectl apply -f ingress-contour.yaml
# Wait for rollout
kubectl rollout status deployment/nora -n nora-system
Terminal window
# Deploy everything
kubectl apply -k .
# Verify
kubectl get all -n nora-system
Terminal window
helm install nora . -n nora-system --create-namespace

Terminal window
# Check pod status
kubectl get pods -n nora-system
# Check logs
kubectl logs -n nora-system -l app=nora -f
# Check service
kubectl get svc -n nora-system
# Test from inside cluster
kubectl 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/

Terminal window
docker login nora.example.com
docker tag myapp:latest nora.example.com/myapp:latest
docker push nora.example.com/myapp:latest
Terminal window
# Create secret
kubectl create secret docker-registry nora-registry \
--docker-server=nora.example.com \
--docker-username=admin \
--docker-password=changeme \
-n default
# Use in pod
spec:
imagePullSecrets:
- name: nora-registry
containers:
- name: myapp
image: nora.example.com/myapp:latest

Pros:

  • Fast
  • Simple

Cons:

  • Not scalable
  • Pod tied to specific node

Best for: Single-node, development

Pros:

  • Shared across nodes
  • Easy backup

Cons:

  • Network latency
  • Single point of failure

Best for: Small clusters, on-premise

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-key

Best for: Production, multi-region


apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: nora
namespace: nora-system
spec:
selector:
matchLabels:
app: nora
endpoints:
- port: api
path: /metrics
interval: 30s

See Monitoring Guide for dashboard JSON.


Problem: Pod stuck in Pending

Terminal window
kubectl describe pod -n nora-system -l app=nora

Check PVC status and node affinity.

Problem: CrashLoopBackOff

Terminal window
kubectl logs -n nora-system -l app=nora --previous

Check volume permissions and configuration.

Problem: 404 on /v2/

Check Ingress configuration and service endpoints:

Terminal window
kubectl get endpoints -n nora-system
kubectl describe ingress nora -n nora-system

  1. Enable authentication (basic or OIDC)
  2. Use NetworkPolicies to restrict access
  3. Enable Pod Security Standards
  4. Rotate credentials regularly
  5. Scan NORA image for vulnerabilities
  6. Use read-only root filesystem where possible
  7. Limit resource usage with ResourceQuotas