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:
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:
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:
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:
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:
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-nam e > :/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-nam e > :/appdata
Troubleshooting
Pods Not Starting
# Check pod status
kubectl get pods -n homarr
# Describe pod for events
kubectl describe pod -n homarr < pod-nam e >
# Check logs
kubectl logs -n homarr < pod-nam e >
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
Use external databases - Don’t run SQLite with multiple replicas
Enable external Redis - Required for distributed caching across pods
Set resource limits - Prevent resource exhaustion
Use ReadWriteMany PVCs - When running multiple replicas with shared storage
Implement health checks - Ensure automatic recovery from failures
Use ConfigMaps and Secrets - Separate configuration from code
Enable autoscaling - Handle varying load automatically
Implement PodDisruptionBudgets - Maintain availability during updates
Regular backups - Automate database and volume backups
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