This guide will help you install the Frappe Operator and deploy your first Frappe site.
Before you begin, ensure you have:
Install the operator and its CRDs:
# Install CRDs and operator
kubectl apply -f https://raw.githubusercontent.com/vyogotech/frappe-operator/main/config/install.yaml
# Verify installation
kubectl get deployment -n frappe-operator-system
kubectl get crd | grep vyogo.tech
You should see the following CRDs:
frappebenchs.vyogo.techfrappesites.vyogo.techsiteusers.vyogo.techsiteworkspaces.vyogo.techsitedashboards.vyogo.techsitedashboardcharts.vyogo.techsitebackups.vyogo.techsitejobs.vyogo.techFor a quick development setup, install a simple MariaDB instance:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb
namespace: default
spec:
serviceName: mariadb
replicas: 1
selector:
matchLabels:
app: mariadb
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb:10.6
env:
- name: MYSQL_ROOT_PASSWORD
value: "admin"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mariadb-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mariadb-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: mariadb
namespace: default
spec:
type: ClusterIP
selector:
app: mariadb
ports:
- port: 3306
targetPort: 3306
EOF
For production, use MariaDB Operator instead.
If you want external access to your sites:
# Install NGINX Ingress Controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
# Wait for it to be ready
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
Create a file named my-first-site.yaml:
---
# FrappeBench: Shared infrastructure
apiVersion: vyogo.tech/v1alpha1
kind: FrappeBench
metadata:
name: dev-bench
namespace: default
spec:
frappeVersion: "version-15"
appsJSON: '["erpnext"]'
---
# FrappeSite: Your site
apiVersion: vyogo.tech/v1alpha1
kind: FrappeSite
metadata:
name: mysite
namespace: default
spec:
benchRef:
name: dev-bench
siteName: "mysite.local"
dbConfig:
mode: shared
Deploy it:
# Apply the manifest
kubectl apply -f my-first-site.yaml
# Watch the resources being created
kubectl get frappebench,frappesite -w
# Download and apply the minimal example
kubectl apply -f https://raw.githubusercontent.com/vyogotech/frappe-operator/main/examples/minimal-bench-and-site.yaml
# Check status
kubectl get frappebench,frappesite
When you deploy a bench and site, the operator creates:
For FrappeBench:
For FrappeSite:
# Check bench status
kubectl get frappebench dev-bench -o yaml
# Check site status
kubectl get frappesite mysite -o yaml
# View all Frappe resources
kubectl get frappebench,frappesite
# View all pods
kubectl get pods
# Check deployments
kubectl get deployments
# Check services
kubectl get services
# Check jobs
kubectl get jobs
# Bench initialization
kubectl logs -l job-name=dev-bench-init -f
# Site initialization
kubectl logs -l job-name=mysite-init -f
# Application logs
kubectl logs -l app=mysite-gunicorn -f
# Forward nginx service to localhost
kubectl port-forward service/dev-bench-nginx 8080:8080
# Add to /etc/hosts (Linux/Mac)
echo "127.0.0.1 mysite.local" | sudo tee -a /etc/hosts
# Or on Windows (as Administrator)
# echo 127.0.0.1 mysite.local >> C:\Windows\System32\drivers\etc\hosts
# Access in browser
# http://mysite.local:8080
If you have an ingress controller and proper DNS:
apiVersion: vyogo.tech/v1alpha1
kind: FrappeSite
metadata:
name: mysite
spec:
benchRef:
name: dev-bench
siteName: "mysite.example.com"
domain: "mysite.example.com"
ingress:
enabled: true
className: "nginx"
tls:
enabled: true
certManagerIssuer: "letsencrypt-prod"
dbConfig:
mode: shared
Access at: https://mysite.example.com
For local development without DNS:
# Expose nginx service as NodePort
kubectl patch service dev-bench-nginx -p '{"spec":{"type":"NodePort"}}'
# Get the NodePort
kubectl get service dev-bench-nginx
# Access via NodePort
# http://<node-ip>:<node-port>
After site initialization, use these default credentials:
Administratoradmin (or check the secret if custom password was set)# Get admin password from secret (if configured)
kubectl get secret mysite-admin-password -o jsonpath='{.data.password}' | base64 -d
kubectl get frappebench dev-bench -o jsonpath='{.status.ready}'
# Should return: true
kubectl get frappesite mysite -o jsonpath='{.status.phase}'
# Should return: Ready
# Get site URL
kubectl get frappesite mysite -o jsonpath='{.status.siteURL}'
# Test with curl
curl -H "Host: mysite.local" http://localhost:8080/api/method/ping
# Should return: {"message":"pong"}
# All pods should be running
kubectl get pods -l bench=dev-bench
kubectl get pods -l site=mysite
# Check endpoints
kubectl get endpoints
# Create a password secret
kubectl create secret generic mysite-admin-pwd \
--from-literal=password='YourSecurePassword123!'
# Reference it in FrappeSite
kubectl patch frappesite mysite --type=merge -p '{
"spec": {
"adminPasswordSecretRef": {
"name": "mysite-admin-pwd"
}
}
}'
# Update bench with more apps
kubectl patch frappebench dev-bench --type=merge -p '{
"spec": {
"appsJSON": "[\"erpnext\", \"hrms\", \"custom_app\"]"
}
}'
# The operator will handle the update
# Scale gunicorn replicas manually
kubectl patch frappebench dev-bench --type=merge -p '{
"spec": {
"componentReplicas": {
"gunicorn": 3,
"workerDefault": 2
}
}
}'
For production workloads, enable KEDA-based autoscaling to automatically scale workers based on queue length:
kubectl patch frappebench dev-bench --type=merge -p '{
"spec": {
"workerAutoscaling": {
"short": {
"enabled": true,
"minReplicas": 0,
"maxReplicas": 10,
"queueLength": 2
},
"long": {
"enabled": true,
"minReplicas": 1,
"maxReplicas": 5,
"queueLength": 5
},
"default": {
"enabled": false,
"staticReplicas": 2
}
}
}
}'
# Check autoscaling status
kubectl get scaledobjects
kubectl get frappebench dev-bench -o jsonpath='{.status.workerScaling}' | jq
Benefits:
For more details, see Worker Autoscaling.
Now that you have a working installation:
If something goes wrong, check:
kubectl logs -n frappe-operator-system deployment/frappe-operator-controller-manager
kubectl describe frappebench dev-bench
kubectl describe frappesite mysite
kubectl logs <pod-name>
For more detailed troubleshooting, see the Troubleshooting Guide.
The operator provides flexible security context configuration for different environments.
Out of the box, the operator uses OpenShift-compatible defaults:
runAsUser: 1001 (OpenShift arbitrary UID)runAsGroup: 0 (root group for OpenShift)fsGroup: 0No configuration needed for OpenShift deployments!
Configure security context for a specific bench:
apiVersion: vyogo.tech/v1alpha1
kind: FrappeBench
metadata:
name: custom-bench
spec:
security:
podSecurityContext:
runAsUser: 2000 # Custom UID
runAsGroup: 2000
fsGroup: 2000
securityContext:
runAsUser: 2000
runAsGroup: 2000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
apps:
- name: erpnext
Set environment variables in the operator deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frappe-operator-controller-manager
namespace: frappe-operator-system
spec:
template:
spec:
containers:
- name: manager
env:
- name: FRAPPE_DEFAULT_UID
value: "2000" # All benches default to UID 2000
- name: FRAPPE_DEFAULT_GID
value: "2000"
- name: FRAPPE_DEFAULT_FSGROUP
value: "2000"
Priority: spec.security → Environment Variables → Hardcoded Defaults (1001/0/0)
For detailed examples and best practices, see:
To remove everything:
# Delete site
kubectl delete frappesite mysite
# Delete bench
kubectl delete frappebench dev-bench
# Delete MariaDB (if you created it)
kubectl delete statefulset mariadb
kubectl delete service mariadb
# Uninstall operator (optional)
kubectl delete -f https://raw.githubusercontent.com/vyogotech/frappe-operator/main/config/install.yaml