Deploy MariaDB on Kubernetes with MariaDB operator
This guide will walk you through deploying MariaDB on a Cloudfleet Kubernetes Engine (CFKE) cluster using the MariaDB operator. MariaDB is fully compatible with MySQL, serving as a drop-in replacement with enhanced features and performance. The MariaDB operator enables you to declaratively manage MariaDB instances using Kubernetes Custom Resource Definitions (CRDs), providing features like high availability, automated backups, and seamless scaling.
What is a Kubernetes operator?
A Kubernetes operator is like having a database expert that automates all the complex operational tasks for your MariaDB deployment. It transforms sophisticated database management into simple, declarative configurations.
While standard Kubernetes resources like Deployments can run your applications, they treat databases like any other container. But databases have unique requirements: careful initialization, coordinated clustering, comprehensive backup strategies, and rolling updates that preserve data integrity.
A Kubernetes operator bridges this gap by encoding operational best practices directly into your cluster. For MariaDB, the operator automatically handles:
- Database initialization – Configures databases with optimal settings from the start
- Automated backups – Schedules and manages backups reliably
- High availability clustering – Orchestrates multi-node setups that survive failures
- Safe rolling updates – Deploys changes while maintaining data consistency
- Health monitoring – Continuously monitors database performance and status
The power comes from declarative management. Instead of writing scripts or running manual commands, you simply describe what you want in a YAML file – like “a 3-node MariaDB cluster with daily backups”, and the operator handles all the implementation details automatically.
Prerequisites
Before getting started, ensure you have:
- A running Cloudfleet Kubernetes Engine (CFKE) cluster (see the getting started guide if you need to set one up)
kubectlconfigured to interact with your cluster (see the Cloudfleet CLI configuration guide if needed)- Helm installed on your local machine
- Storage support configured on your cluster (see storage requirements below)
Storage requirements
MariaDB requires persistent storage to maintain data across pod restarts. Your CFKE cluster must have a storage provisioner configured.
For Hetzner-based clusters
If your cluster uses Hetzner nodes, follow the persistent volumes with Cloudfleet on Hetzner tutorial to set up the Hetzner Cloud CSI driver on your CFKE cluster.
For self-managed nodes
If you’re using self-managed nodes, you can set up the local-path-provisioner:
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
This creates a local-path storage class that uses local node storage. While suitable for many use cases, be aware that local storage is tied to specific nodes. For production environments requiring high availability across multiple nodes, consider distributed storage solutions like Ceph, Longhorn, or network-attached storage.
Step 1: Install the MariaDB operator
The MariaDB operator consists of two Helm charts: the CRDs and the operator itself.
First, add the MariaDB operator Helm repository:
helm repo add mariadb-operator https://helm.mariadb.com/mariadb-operator
helm repo update
Install the Custom Resource Definitions (CRDs):
helm install mariadb-operator-crds mariadb-operator/mariadb-operator-crds
Install the MariaDB operator:
helm install mariadb-operator mariadb-operator/mariadb-operator
Step 2: Verify the operator installation
Check that the operator pods are running:
kubectl get pods -n default -l app.kubernetes.io/name=mariadb-operator
You should see the MariaDB operator pod in a Running state.
Verify that the MariaDB CRDs have been installed:
kubectl get crd | grep mariadb
This should show several CRDs including mariadbs.k8s.mariadb.com.
Step 3: Create a basic MariaDB instance
Now you can deploy a MariaDB instance using the operator. Create a file named mariadb-basic.yaml:
apiVersion: v1
kind: Secret
metadata:
name: mariadb-root-password
type: Opaque
stringData:
password: "your-secure-root-password"
---
apiVersion: v1
kind: Secret
metadata:
name: mariadb-user-password
type: Opaque
stringData:
password: "your-secure-app-user-password"
---
apiVersion: k8s.mariadb.com/v1alpha1
kind: MariaDB
metadata:
name: mariadb-basic
spec:
rootPasswordSecretKeyRef:
name: mariadb-root-password
key: password
username: app-user
passwordSecretKeyRef:
name: mariadb-user-password
key: password
database: myapp
port: 3306
storage:
size: 5Gi
storageClassName: hcloud-volumes # Use "local-path" for self-managed nodes
nodeSelector:
cfke.io/provider: hetzner # Required when using hcloud-volumes storage class
myCnf: |
[mariadb]
bind-address=*
default_storage_engine=InnoDB
binlog_format=row
innodb_autoinc_lock_mode=2
max_allowed_packet=256M
resources:
# Choose appropriate resource requests for the MariaDB pod
requests:
cpu: 1000m
memory: 512Mi
metrics:
enabled: true
Important notes about this configuration:
- Replace
"your-secure-root-password"and"your-secure-app-user-password"with strong passwords - Use
storageClassName: hcloud-volumesfor Hetzner clusters orstorageClassName: local-pathfor self-managed nodes - Critical for Hetzner storage: The
nodeSelector: cfke.io/provider: hetznerensures MariaDB pods run on Hetzner nodes. Hetzner Cloud volumes can only be attached to Hetzner nodes, so omitting this will cause storage attachment failures. If your cluster only contains Hetzner nodes, this nodeSelector is optional but recommended for clarity - Remove the
nodeSelectorwhen usinglocal-pathstorage class for self-managed nodes - We’re creating the password secrets explicitly rather than letting the operator generate them, which makes cross-namespace usage simpler
- Metrics are enabled for monitoring with Prometheus
Apply the configuration:
kubectl apply -f mariadb-basic.yaml
Step 4: Monitor the deployment
Watch the MariaDB instance being created:
kubectl get mariadb mariadb-basic -w
The status will progress from Provisioning to Ready once the database is fully deployed.
Check the created pods:
kubectl get pods -l app.kubernetes.io/instance=mariadb-basic
You should see a MariaDB pod running.
Verify the persistent volume claim:
kubectl get pvc -l app.kubernetes.io/instance=mariadb-basic
Step 5: Connect to your MariaDB instance
Get connection credentials
Retrieve the generated application user password:
kubectl get secret mariadb-user-password -o jsonpath='{.data.password}' | base64 -d
Connect using kubectl port-forward
Forward the MariaDB port to your local machine:
kubectl port-forward svc/mariadb-basic 3306:3306
In another terminal, connect using the MariaDB client:
mysql -h 127.0.0.1 -P 3306 -u app-user -p myapp
Enter the password you retrieved in the previous step.
Create a connection resource
For applications running in the cluster, you can create a Connection resource that provides connection details. The Connection resource creates a standardized way to expose database connection information, including connection strings and credentials, making them easily accessible to applications.
Create mariadb-connection.yaml:
apiVersion: k8s.mariadb.com/v1alpha1
kind: Connection
metadata:
name: mariadb-basic-connection
spec:
mariaDbRef:
name: mariadb-basic
username: app-user
passwordSecretKeyRef:
name: mariadb-user-password
key: password
database: myapp
Apply the connection:
kubectl apply -f mariadb-connection.yaml
Applications can now reference this connection to get database credentials and connection details. Let’s test the connection using a MySQL client pod to verify everything works correctly:
apiVersion: batch/v1
kind: Job
metadata:
name: mariadb-test
spec:
template:
spec:
containers:
- name: mysql-client
image: mariadb:11.4
env:
- name: MYSQL_PWD
valueFrom:
secretKeyRef:
name: mariadb-user-password
key: password
command:
- bash
- -c
- |
echo "Testing MariaDB connection..."
mariadb -h mariadb-basic -u app-user myapp \
--ssl-ca=/etc/ssl/mariadb/ca.crt \
--ssl-cert=/etc/ssl/mariadb/tls.crt \
--ssl-key=/etc/ssl/mariadb/tls.key \
--ssl-verify-server-cert \
-e "SELECT 'Connection successful!' AS Status, NOW() AS CurrentTime;"
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/mariadb
readOnly: true
volumes:
- name: tls-certs
projected:
sources:
- secret:
name: mariadb-basic-ca-bundle # CA bundle for server verification
items:
- key: ca.crt
path: ca.crt
- secret:
name: mariadb-basic-client-cert # Client certificate for authentication
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
restartPolicy: Never
Apply the test job:
kubectl apply -f mariadb-test.yaml
Check the test results:
kubectl logs job/mariadb-test
If successful, you should see output like:
Testing MariaDB connection...
+------------------------+---------------------+
| Status | CurrentTime |
+------------------------+---------------------+
| Connection successful! | 2025-09-27 09:30:45 |
+------------------------+---------------------+
The Connection resource automatically creates a secret with a ready-to-use DSN connection string. You can inspect the generated DSN:
kubectl get secret mariadb-basic-connection -o json | jq -r '.data.dsn' | base64 -d
This will show a connection string like:
app-user:o%4f9BdmENb|6p5Z@tcp(mariadb-basic.default.svc.cluster.local:3306)/myapp?timeout=5s&tls=mariadb-mariadb-basic-default-client-mariadb-basic-client-cert
Note that the MariaDB operator enables TLS by default, which is why the DSN includes TLS parameter parameters for secure connections. The Connection resource automatically handles TLS configuration and provides a ready-to-use connection string. Applications need to mount the TLS certificates to establish secure connections - the CA bundle for server verification and the client certificate for authentication.
For production applications, you can use a similar pattern but with a Deployment instead of a Job:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:latest
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: mariadb-basic-connection # Secret created by the Connection resource
key: dsn
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/mariadb
readOnly: true
volumes:
- name: tls-certs
projected:
sources:
- secret:
name: mariadb-basic-ca-bundle # CA bundle for server verification
items:
- key: ca.crt
path: ca.crt
- secret:
name: mariadb-basic-client-cert # Client certificate for authentication
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
Step 6: Secure your database with TLS enforcement and NetworkPolicy
TLS enforcement
While MariaDB operator enables TLS by default, it allows both encrypted and unencrypted connections. For enhanced security, you can enforce TLS to reject any unencrypted connection attempts:
apiVersion: k8s.mariadb.com/v1alpha1
kind: MariaDB
metadata:
name: mariadb-basic
spec:
# ... other configuration ...
tls:
enabled: true
required: true # Enforce TLS - reject unencrypted connections
Apply this change to your existing MariaDB instance:
kubectl patch mariadb mariadb-basic --type='merge' -p='{"spec":{"tls":{"enabled":true,"required":true}}}'
This will trigger a rolling update to enforce TLS connections.
NetworkPolicy for network-level security
CFKE provides NetworkPolicy support to control network traffic between pods. This adds an additional layer of security that’s particularly important in multi-tenancy scenarios where multiple applications share the same cluster. You can use NetworkPolicy to ensure only authorized applications can connect to your MariaDB instance:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mariadb-access-policy
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: mariadb
app.kubernetes.io/instance: mariadb-basic
policyTypes:
- Ingress
ingress:
- from:
# Allow connections from pods with specific labels
- podSelector:
matchLabels:
app: my-app
# Allow connections from the test job
- podSelector:
matchLabels:
job-name: mariadb-test
ports:
- protocol: TCP
port: 3306
Apply the NetworkPolicy:
kubectl apply -f mariadb-network-policy.yaml
This policy ensures that only pods with the label app: my-app or job-name: mariadb-test can connect to your MariaDB instance on port 3306. All other network traffic to the database will be blocked, providing an additional layer of security.
Why both TLS enforcement AND NetworkPolicy?
- TLS enforcement secures the data in transit - even if someone intercepts network traffic, they cannot read the encrypted database communications
- NetworkPolicy controls WHO can establish connections - preventing unauthorized applications from even attempting to connect to your database
- Multi-tenancy protection - In shared clusters where multiple teams deploy applications, NetworkPolicy prevents Team A’s applications from accidentally or maliciously accessing Team B’s databases, even if they somehow obtain valid credentials
Backup and restore
The MariaDB operator supports automated backups. Create a backup configuration:
apiVersion: k8s.mariadb.com/v1alpha1
kind: Backup
metadata:
name: mariadb-backup
spec:
mariaDbRef:
name: mariadb-basic
schedule:
cron: "0 2 * * *" # Daily at 2 AM
storage:
persistentVolumeClaim:
resources:
requests:
storage: 1Gi
storageClassName: hcloud-volumes
retentionPolicy:
cleanupPolicy: Delete
daysToRetain: 30
Conclusion
You’ve successfully deployed MariaDB on your CFKE cluster using the MariaDB operator. This setup provides:
- Declarative database management through Kubernetes CRDs
- Persistent storage for data durability
- Built-in monitoring capabilities
- Options for high availability with Galera clustering
- Automated backup and restore functionality
The MariaDB operator simplifies database operations in Kubernetes while providing enterprise-grade features for production workloads. You can now deploy applications that require MariaDB with confidence, knowing your database infrastructure is managed declaratively and can scale with your needs.
Next steps
Now that you have a basic MariaDB deployment running, you might want to explore more advanced features:
High availability and clustering
- Galera clustering - Deploy multi-node MariaDB clusters with automatic failover (Galera documentation)
- MaxScale - Add database proxy and load balancing capabilities (MaxScale documentation)
Advanced backup strategies
- S3 backup storage - Configure backups to cloud storage (Backup documentation)
- Backup encryption - Secure your backup data with encryption (Physical backup documentation)
Monitoring and observability
- Metrics collection - Enable Prometheus metrics for database monitoring (configured via the
metrics.enabled: truesetting shown in this tutorial)
Production hardening
- Resource quotas - Set appropriate CPU and memory limits for production (Resource management)
- Pod disruption budgets - Ensure availability during cluster maintenance (High availability documentation)
For detailed information on these advanced features, refer to the MariaDB operator documentation.