Skip to main content

Overview

Deploying Homarr on Kubernetes provides high availability, automatic scaling, and robust orchestration capabilities. This guide covers various deployment strategies from simple single-instance setups to production-grade highly available configurations.
Homarr supports Kubernetes deployments with features like external Redis and database support for distributed caching and data persistence.

Prerequisites

  • Kubernetes cluster 1.20+ (Minikube, k3s, EKS, GKE, AKS, etc.)
  • kubectl configured to access your cluster
  • Basic understanding of Kubernetes concepts (Pods, Services, Deployments)
  • Optional: Helm 3+ for easier deployment

Architecture

A typical Homarr Kubernetes deployment consists of:

Basic Deployment

Simple Single Pod Deployment

Create homarr-deployment.yaml:
homarr-deployment.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: homarr

---
apiVersion: v1
kind: Secret
metadata:
  name: homarr-secrets
  namespace: homarr
type: Opaque
stringData:
  secret-encryption-key: "0000000000000000000000000000000000000000000000000000000000000000"

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: homarr-data
  namespace: homarr
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: homarr
  namespace: homarr
  labels:
    app: homarr
spec:
  replicas: 1
  selector:
    matchLabels:
      app: homarr
  template:
    metadata:
      labels:
        app: homarr
    spec:
      containers:
      - name: homarr
        image: ghcr.io/homarr-labs/homarr:latest
        ports:
        - containerPort: 7575
          name: http
        env:
        - name: SECRET_ENCRYPTION_KEY
          valueFrom:
            secretKeyRef:
              name: homarr-secrets
              key: secret-encryption-key
        - name: LOG_LEVEL
          value: "info"
        - name: DB_DRIVER
          value: "better-sqlite3"
        - name: DB_URL
          value: "/appdata/db/db.sqlite"
        volumeMounts:
        - name: data
          mountPath: /appdata
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /
            port: 7575
          initialDelaySeconds: 60
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 7575
          initialDelaySeconds: 30
          periodSeconds: 5
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: homarr-data

---
apiVersion: v1
kind: Service
metadata:
  name: homarr
  namespace: homarr
  labels:
    app: homarr
spec:
  type: ClusterIP
  ports:
  - port: 7575
    targetPort: 7575
    protocol: TCP
    name: http
  selector:
    app: homarr
Deploy:
kubectl apply -f homarr-deployment.yaml
Replace the secret-encryption-key with a real 64-character hex string generated by openssl rand -hex 32.

Expose with Ingress

Create homarr-ingress.yaml:
homarr-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: homarr
  namespace: homarr
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-body-size: "32m"
    nginx.ingress.kubernetes.io/websocket-services: "homarr"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - homarr.example.com
    secretName: homarr-tls
  rules:
  - host: homarr.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: homarr
            port:
              number: 7575
Apply:
kubectl apply -f homarr-ingress.yaml

Production Deployment with PostgreSQL

For production, use an external database like PostgreSQL:
homarr-production.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: homarr

---
apiVersion: v1
kind: Secret
metadata:
  name: homarr-secrets
  namespace: homarr
type: Opaque
stringData:
  secret-encryption-key: "your-64-character-hex-string"
  db-password: "secure-database-password"

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: homarr-config
  namespace: homarr
data:
  LOG_LEVEL: "info"
  DB_DRIVER: "node-postgres"
  REDIS_IS_EXTERNAL: "true"
  REDIS_HOST: "redis"
  REDIS_PORT: "6379"

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: homarr
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16-alpine
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_USER
          value: homarr
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: homarr-secrets
              key: db-password
        - name: POSTGRES_DB
          value: homarrdb
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
  volumeClaimTemplates:
  - metadata:
      name: postgres-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: homarr
spec:
  clusterIP: None
  ports:
  - port: 5432
    targetPort: 5432
  selector:
    app: postgres

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: homarr
spec:
  serviceName: redis
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command: ["redis-server", "--appendonly", "yes"]
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: redis-data
          mountPath: /data
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: homarr
spec:
  clusterIP: None
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: homarr-data
  namespace: homarr
spec:
  accessModes:
    - ReadWriteMany  # Use ReadWriteMany for multiple replicas
  resources:
    requests:
      storage: 5Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: homarr
  namespace: homarr
  labels:
    app: homarr
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: homarr
  template:
    metadata:
      labels:
        app: homarr
    spec:
      containers:
      - name: homarr
        image: ghcr.io/homarr-labs/homarr:latest
        ports:
        - containerPort: 7575
          name: http
        env:
        - name: SECRET_ENCRYPTION_KEY
          valueFrom:
            secretKeyRef:
              name: homarr-secrets
              key: secret-encryption-key
        - name: DB_URL
          value: "postgres://homarr:$(DB_PASSWORD)@postgres:5432/homarrdb"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: homarr-secrets
              key: db-password
        envFrom:
        - configMapRef:
            name: homarr-config
        volumeMounts:
        - name: data
          mountPath: /appdata
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        livenessProbe:
          httpGet:
            path: /
            port: 7575
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /
            port: 7575
          initialDelaySeconds: 30
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: homarr-data

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

Kubernetes Features

Horizontal Pod Autoscaling

Create homarr-hpa.yaml:
homarr-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: homarr
  namespace: homarr
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: homarr
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Pod Disruption Budget

Ensure availability during maintenance:
homarr-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: homarr
  namespace: homarr
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: homarr

Network Policies

Restrict network access:
homarr-networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: homarr
  namespace: homarr
spec:
  podSelector:
    matchLabels:
      app: homarr
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 7575
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - protocol: TCP
      port: 6379
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 53
    - protocol: UDP
      port: 53

Monitoring and Observability

Add Prometheus Annotations

metadata:
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "7575"
    prometheus.io/path: "/metrics"

Create ServiceMonitor (for Prometheus Operator)

homarr-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: homarr
  namespace: homarr
spec:
  selector:
    matchLabels:
      app: homarr
  endpoints:
  - port: http
    interval: 30s

Management Commands

View Resources

# List all resources in homarr namespace
kubectl get all -n homarr

# Get pod details
kubectl describe pod -n homarr -l app=homarr

# View logs
kubectl logs -n homarr -l app=homarr -f

Scale Deployment

# Scale to 3 replicas
kubectl scale deployment homarr -n homarr --replicas=3

# View scaling status
kubectl get pods -n homarr -w

Update Configuration

# Edit ConfigMap
kubectl edit configmap homarr-config -n homarr

# Restart pods to pick up changes
kubectl rollout restart deployment homarr -n homarr

Access Homarr CLI

# Get pod name
POD=$(kubectl get pod -n homarr -l app=homarr -o jsonpath="{.items[0].metadata.name}")

# Run CLI command
kubectl exec -n homarr $POD -- homarr --help

# Reset password
kubectl exec -n homarr $POD -- homarr reset-password admin

Backup and Restore

Backup Strategy

Create a CronJob for automated backups:
homarr-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: homarr-backup
  namespace: homarr
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: postgres:16-alpine
            command:
            - /bin/sh
            - -c
            - |
              pg_dump -h postgres -U homarr homarrdb | \
              gzip > /backup/homarr-$(date +%Y%m%d-%H%M%S).sql.gz
            env:
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: homarr-secrets
                  key: db-password
            volumeMounts:
            - name: backup
              mountPath: /backup
          restartPolicy: OnFailure
          volumes:
          - name: backup
            persistentVolumeClaim:
              claimName: homarr-backup

Manual Backup

# Backup PostgreSQL database
kubectl exec -n homarr postgres-0 -- \
  pg_dump -U homarr homarrdb | \
  gzip > homarr-backup-$(date +%Y%m%d).sql.gz

# Backup PVC data
kubectl cp homarr/<pod-name>:/appdata ./homarr-data-backup

Restore from Backup

# Restore PostgreSQL database
gunzip -c homarr-backup-20240101.sql.gz | \
  kubectl exec -i -n homarr postgres-0 -- \
  psql -U homarr homarrdb

# Restore PVC data
kubectl cp ./homarr-data-backup homarr/<pod-name>:/appdata

Troubleshooting

Pods Not Starting

# Check pod status
kubectl get pods -n homarr

# Describe pod for events
kubectl describe pod -n homarr <pod-name>

# Check logs
kubectl logs -n homarr <pod-name>

Database Connection Issues

# Test database connectivity
kubectl run -it --rm debug --image=postgres:16-alpine --restart=Never -n homarr -- \
  psql -h postgres -U homarr -d homarrdb

# Check service endpoints
kubectl get endpoints -n homarr postgres

PVC Not Mounting

# Check PVC status
kubectl get pvc -n homarr

# Describe PVC
kubectl describe pvc -n homarr homarr-data

# Check storage class
kubectl get storageclass

Best Practices

  1. Use external databases - Don’t run SQLite with multiple replicas
  2. Enable external Redis - Required for distributed caching across pods
  3. Set resource limits - Prevent resource exhaustion
  4. Use ReadWriteMany PVCs - When running multiple replicas with shared storage
  5. Implement health checks - Ensure automatic recovery from failures
  6. Use ConfigMaps and Secrets - Separate configuration from code
  7. Enable autoscaling - Handle varying load automatically
  8. Implement PodDisruptionBudgets - Maintain availability during updates
  9. Regular backups - Automate database and volume backups
  10. Monitor metrics - Use Prometheus and Grafana for observability

Next Steps

Environment Variables

Configure environment variables

Authentication

Set up authentication providers

Backup & Restore

Backup your Kubernetes deployment

Troubleshooting

Common Kubernetes issues

Build docs developers (and LLMs) love