This guide covers deploying Firefly III on Kubernetes, including configuration for persistent storage, secrets management, and scalability considerations.
Prerequisites
Kubernetes cluster 1.24 or higher
kubectl configured to access your cluster
A storage class for persistent volumes
(Optional) Ingress controller (nginx, Traefik, etc.)
(Optional) cert-manager for SSL certificates
Quick Start
Create a namespace
kubectl create namespace firefly
Deploy the manifests
Apply all the manifests from the following sections, or use Helm (see below).
Access the application
Use port-forward to test: kubectl port-forward -n firefly svc/firefly 8080:80
Then navigate to http://localhost:8080.
Kubernetes Manifests
Namespace
apiVersion : v1
kind : Namespace
metadata :
name : firefly
Secrets
Create secrets for sensitive data:
apiVersion : v1
kind : Secret
metadata :
name : firefly-secrets
namespace : firefly
type : Opaque
stringData :
APP_KEY : "SomeRandomStringOf32CharsExactly"
DB_PASSWORD : "secure_database_password"
MYSQL_ROOT_PASSWORD : "secure_root_password"
SITE_OWNER : "[email protected] "
Generate a secure APP_KEY: head /dev/urandom | LC_ALL = C tr -dc 'A-Za-z0-9' | head -c 32 && echo
Replace the value in the secret before applying!
Apply the secret:
kubectl apply -f secrets.yaml
ConfigMap
Store non-sensitive configuration:
apiVersion : v1
kind : ConfigMap
metadata :
name : firefly-config
namespace : firefly
data :
APP_ENV : "production"
APP_DEBUG : "false"
APP_URL : "https://firefly.example.com"
DB_CONNECTION : "mysql"
DB_HOST : "firefly-db"
DB_PORT : "3306"
DB_DATABASE : "firefly"
DB_USERNAME : "firefly"
DEFAULT_LANGUAGE : "en_US"
DEFAULT_LOCALE : "equal"
TZ : "Europe/Amsterdam"
TRUSTED_PROXIES : "**"
LOG_CHANNEL : "stack"
APP_LOG_LEVEL : "notice"
AUDIT_LOG_LEVEL : "emergency"
CACHE_DRIVER : "file"
SESSION_DRIVER : "file"
PersistentVolumeClaims
Define storage for the database and uploads:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : firefly-db-pvc
namespace : firefly
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 10Gi
# storageClassName: your-storage-class # Uncomment and specify if needed
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : firefly-upload-pvc
namespace : firefly
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 5Gi
# storageClassName: your-storage-class
Database Deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : firefly-db
namespace : firefly
labels :
app : firefly-db
spec :
replicas : 1
selector :
matchLabels :
app : firefly-db
strategy :
type : Recreate
template :
metadata :
labels :
app : firefly-db
spec :
containers :
- name : mariadb
image : mariadb:latest
env :
- name : MYSQL_ROOT_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : MYSQL_ROOT_PASSWORD
- name : MYSQL_DATABASE
value : firefly
- name : MYSQL_USER
value : firefly
- name : MYSQL_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : DB_PASSWORD
ports :
- containerPort : 3306
name : mysql
volumeMounts :
- name : db-storage
mountPath : /var/lib/mysql
resources :
requests :
memory : "256Mi"
cpu : "250m"
limits :
memory : "1Gi"
cpu : "1000m"
livenessProbe :
exec :
command :
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
exec :
command :
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds : 5
periodSeconds : 5
volumes :
- name : db-storage
persistentVolumeClaim :
claimName : firefly-db-pvc
---
apiVersion : v1
kind : Service
metadata :
name : firefly-db
namespace : firefly
spec :
selector :
app : firefly-db
ports :
- port : 3306
targetPort : 3306
clusterIP : None
Firefly III Deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : firefly-app
namespace : firefly
labels :
app : firefly-app
spec :
replicas : 1 # Scale to 2+ for high availability
selector :
matchLabels :
app : firefly-app
strategy :
type : RollingUpdate
rollingUpdate :
maxSurge : 1
maxUnavailable : 0
template :
metadata :
labels :
app : firefly-app
spec :
initContainers :
- name : wait-for-db
image : busybox:1.35
command :
- sh
- -c
- |
until nc -z firefly-db 3306; do
echo "Waiting for database..."
sleep 2
done
containers :
- name : firefly
image : fireflyiii/core:latest
envFrom :
- configMapRef :
name : firefly-config
env :
- name : APP_KEY
valueFrom :
secretKeyRef :
name : firefly-secrets
key : APP_KEY
- name : SITE_OWNER
valueFrom :
secretKeyRef :
name : firefly-secrets
key : SITE_OWNER
- name : DB_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : DB_PASSWORD
ports :
- containerPort : 8080
name : http
volumeMounts :
- name : upload-storage
mountPath : /var/www/html/storage/upload
resources :
requests :
memory : "256Mi"
cpu : "250m"
limits :
memory : "1Gi"
cpu : "1000m"
livenessProbe :
httpGet :
path : /health
port : 8080
initialDelaySeconds : 60
periodSeconds : 10
timeoutSeconds : 5
failureThreshold : 3
readinessProbe :
httpGet :
path : /health
port : 8080
initialDelaySeconds : 30
periodSeconds : 5
timeoutSeconds : 3
failureThreshold : 3
volumes :
- name : upload-storage
persistentVolumeClaim :
claimName : firefly-upload-pvc
---
apiVersion : v1
kind : Service
metadata :
name : firefly
namespace : firefly
spec :
selector :
app : firefly-app
ports :
- port : 80
targetPort : 8080
type : ClusterIP
Ingress (Optional)
Expose Firefly III via an ingress controller:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : firefly-ingress
namespace : firefly
annotations :
cert-manager.io/cluster-issuer : letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size : "100m"
nginx.ingress.kubernetes.io/force-ssl-redirect : "true"
spec :
ingressClassName : nginx
tls :
- hosts :
- firefly.example.com
secretName : firefly-tls
rules :
- host : firefly.example.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : firefly
port :
number : 80
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : firefly-ingress
namespace : firefly
annotations :
traefik.ingress.kubernetes.io/router.entrypoints : websecure
traefik.ingress.kubernetes.io/router.tls : "true"
cert-manager.io/cluster-issuer : letsencrypt-prod
spec :
ingressClassName : traefik
tls :
- hosts :
- firefly.example.com
secretName : firefly-tls
rules :
- host : firefly.example.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : firefly
port :
number : 80
CronJob for Recurring Transactions
Firefly III requires a daily cron job:
apiVersion : batch/v1
kind : CronJob
metadata :
name : firefly-cron
namespace : firefly
spec :
schedule : "0 3 * * *" # Run at 3 AM daily
successfulJobsHistoryLimit : 3
failedJobsHistoryLimit : 3
jobTemplate :
spec :
template :
spec :
restartPolicy : OnFailure
containers :
- name : cron
image : fireflyiii/core:latest
envFrom :
- configMapRef :
name : firefly-config
env :
- name : APP_KEY
valueFrom :
secretKeyRef :
name : firefly-secrets
key : APP_KEY
- name : DB_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : DB_PASSWORD
command :
- php
- artisan
- schedule:run
Deploying All Manifests
Apply all manifests in order:
kubectl apply -f namespace.yaml
kubectl apply -f secrets.yaml
kubectl apply -f configmap.yaml
kubectl apply -f pvc.yaml
kubectl apply -f database-deployment.yaml
kubectl apply -f firefly-deployment.yaml
kubectl apply -f ingress.yaml
kubectl apply -f cronjob.yaml
Using PostgreSQL Instead
To use PostgreSQL instead of MySQL:
Update the ConfigMap:
DB_CONNECTION : "pgsql"
DB_PORT : "5432"
PGSQL_SCHEMA : "public"
Replace the database deployment with:
containers :
- name : postgres
image : postgres:15
env :
- name : POSTGRES_DB
value : firefly
- name : POSTGRES_USER
value : firefly
- name : POSTGRES_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : DB_PASSWORD
High Availability Configuration
Multiple Replicas
Increase the replica count:
When running multiple replicas, ensure your storage supports ReadWriteMany for the upload volume, or use object storage (S3).
Session Affinity
If not using Redis for sessions, enable sticky sessions:
apiVersion : v1
kind : Service
metadata :
name : firefly
namespace : firefly
spec :
selector :
app : firefly-app
sessionAffinity : ClientIP
sessionAffinityConfig :
clientIP :
timeoutSeconds : 3600
ports :
- port : 80
targetPort : 8080
Redis for Caching (Recommended)
Deploy Redis:
apiVersion : apps/v1
kind : Deployment
metadata :
name : redis
namespace : firefly
spec :
replicas : 1
selector :
matchLabels :
app : redis
template :
metadata :
labels :
app : redis
spec :
containers :
- name : redis
image : redis:7-alpine
ports :
- containerPort : 6379
resources :
requests :
memory : "128Mi"
cpu : "100m"
limits :
memory : "256Mi"
cpu : "200m"
---
apiVersion : v1
kind : Service
metadata :
name : redis
namespace : firefly
spec :
selector :
app : redis
ports :
- port : 6379
Update the ConfigMap:
CACHE_DRIVER : "redis"
SESSION_DRIVER : "redis"
REDIS_HOST : "redis"
REDIS_PORT : "6379"
REDIS_DB : "0"
REDIS_CACHE_DB : "1"
Resource Requirements
Minimum Requirements
Firefly III Pod : 256Mi RAM, 250m CPU
Database Pod : 256Mi RAM, 250m CPU
Storage : 10Gi for database, 5Gi for uploads
Recommended for Production
Firefly III Pod : 512Mi-1Gi RAM, 500m-1000m CPU
Database Pod : 1-2Gi RAM, 500m-1000m CPU
Storage : 20Gi+ for database, 10Gi+ for uploads
Backup and Restore
Database Backup
Create a CronJob for regular backups:
apiVersion : batch/v1
kind : CronJob
metadata :
name : firefly-backup
namespace : firefly
spec :
schedule : "0 2 * * *" # Daily at 2 AM
jobTemplate :
spec :
template :
spec :
restartPolicy : OnFailure
containers :
- name : backup
image : mariadb:latest
command :
- sh
- -c
- |
mysqldump -h firefly-db -u firefly -p$DB_PASSWORD firefly | \
gzip > /backup/firefly-$(date +%Y%m%d-%H%M%S).sql.gz
env :
- name : DB_PASSWORD
valueFrom :
secretKeyRef :
name : firefly-secrets
key : DB_PASSWORD
volumeMounts :
- name : backup-storage
mountPath : /backup
volumes :
- name : backup-storage
# Configure your backup storage here
persistentVolumeClaim :
claimName : backup-pvc
Manual Backup
# Find the database pod
DB_POD = $( kubectl get pod -n firefly -l app=firefly-db -o jsonpath="{.items[0].metadata.name}" )
# Create backup
kubectl exec -n firefly $DB_POD -- mysqldump -u firefly -psecure_database_password firefly > backup.sql
Restore from Backup
kubectl exec -i -n firefly $DB_POD -- mysql -u firefly -psecure_database_password firefly < backup.sql
Monitoring
Health Checks
Firefly III provides a health endpoint at /health.
Prometheus Metrics (Optional)
Add ServiceMonitor if using Prometheus Operator:
apiVersion : monitoring.coreos.com/v1
kind : ServiceMonitor
metadata :
name : firefly
namespace : firefly
spec :
selector :
matchLabels :
app : firefly-app
endpoints :
- port : http
path : /metrics
Troubleshooting
Check Pod Status
kubectl get pods -n firefly
kubectl describe pod -n firefly < pod-nam e >
View Logs
# Application logs
kubectl logs -n firefly -l app=firefly-app --tail=100 -f
# Database logs
kubectl logs -n firefly -l app=firefly-db --tail=100 -f
Database Connection Issues
Test database connectivity:
kubectl exec -it -n firefly < firefly-pod-nam e > -- php artisan tinker
Then run:
DB :: connection () -> getPdo ();
Persistent Volume Issues
Check PVC status:
kubectl get pvc -n firefly
kubectl describe pvc -n firefly firefly-db-pvc
Upgrading
Update the image tag
Edit the deployment: kubectl edit deployment -n firefly firefly-app
Change the image to a specific version: image : fireflyiii/core:v6.1.0
Apply the change
Kubernetes will perform a rolling update automatically.
Monitor the rollout
kubectl rollout status deployment/firefly-app -n firefly
Next Steps
Configuration Learn about environment variables and settings
Production Best Practices Secure your Firefly III installation