Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/homarr-labs/homarr/llms.txt

Use this file to discover all available pages before exploring further.

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