MariaDB, a popular open-source relational database, runs well in Kubernetes — especially when paired with tools like Helm and Kustomize for clean, repeatable infrastructure management.
In this guide, you’ll learn how to deploy MariaDB into a Kubernetes cluster using Kustomize (plain YAML + overlays) and optionally Helmfile (Helm releases as code).
Using Helm and Kustomize is beneficial in a couple of ways:
- Helm simplifies complex deployments using charts — reusable application templates.
- Kustomize allows you to declaratively patch and manage multiple environments (dev, staging, prod) without duplicating YAML.
In this guide, you’ll learn how to:
- Deploy MariaDB in Kubernetes
- Manage configuration using Helm and Kustomize
- Secure secrets using External Secrets
- Use Helmfile for simplified release management
Prerequisites
- Kubernetes cluster (local via kind/minikube, or cloud via GKE/EKS/AKS)
kubectl, helm, and kustomize installed- Optional: kubens and kubectx for context/namespace management
Create a Namespace
Isolate the MariaDB resources in a dedicated namespace. Save this under namespace.yaml:
1
2
3
4
5
6
| # namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: mariadb
|
Namespacing keeps resources logically grouped and avoids name collisions.
Define persistent storage
MariaDB stores data on disk, so you want persistent storage. For a StatefulSet, the most common pattern is to use volumeClaimTemplates so each replica gets its own PVC automatically.
Manage Secrets with External Secrets
We need a Kubernetes Secret containing database credentials. A common pattern is to store them in a secret manager and sync them into Kubernetes using External Secrets.
Use External Secrets Operator to securely inject credentials from a secret store (e.g. Vault, AWS Secrets Manager).
Save this under external-secret.yaml.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # external-secret.yaml
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: mariadb-credentials
namespace: mariadb
spec:
dataFrom:
- extract:
key: infra/mariadb-credentials
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: mariadb-credentials
creationPolicy: Owner
|
This keeps credentials out of source control and lets you rotate secrets outside the cluster.
Expected keys in the generated Secret (example):
root-password- (recommended)
username - (recommended)
password - (optional)
database
Deploy MariaDB with Kustomize (StatefulSet)
Databases should usually run as a StatefulSet (stable identity + safer semantics with storage). Save this under statefulset.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
| # statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb
namespace: mariadb
spec:
serviceName: mariadb
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: mariadb
template:
metadata:
labels:
app.kubernetes.io/name: mariadb
spec:
securityContext:
fsGroup: 999
terminationGracePeriodSeconds: 30
containers:
- name: mariadb
image: mariadb:11.7
ports:
- containerPort: 3306
name: mariadb
env:
- name: MARIADB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mariadb-credentials
key: root-password
# Recommended: create an application DB/user (avoid using root for apps)
- name: MARIADB_DATABASE
valueFrom:
secretKeyRef:
name: mariadb-credentials
key: database
optional: true
- name: MARIADB_USER
valueFrom:
secretKeyRef:
name: mariadb-credentials
key: username
optional: true
- name: MARIADB_PASSWORD
valueFrom:
secretKeyRef:
name: mariadb-credentials
key: password
optional: true
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
livenessProbe:
exec:
command:
[
"bash",
"-ec",
'mariadb-admin ping -uroot -p"${MARIADB_ROOT_PASSWORD}"',
]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
[
"bash",
"-ec",
'mariadb-admin ping -uroot -p"${MARIADB_ROOT_PASSWORD}"',
]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
labels:
app.kubernetes.io/name: mariadb
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
# Optional: set a storageClassName depending on your cluster (EBS, Ceph, Longhorn, etc.)
# storageClassName: standard
|
This will create a PVC automatically (named like data-mariadb-0) and mount it at /var/lib/mysql.
Expose MariaDB via Service
We need a way to expose the database inside the cluster. Save this under service.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: mariadb
namespace: mariadb
spec:
clusterIP: None
ports:
- port: 3306
targetPort: mariadb
selector:
app.kubernetes.io/name: mariadb
|
This creates a headless service for the StatefulSet, and other pods can connect using mariadb.mariadb.svc.cluster.local:3306.
We use Kustomize to manage all the YAML files. Bring it all together using a kustomization.yaml file:
1
2
3
4
5
6
7
8
9
10
11
12
| # kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mariadb
resources:
- ./namespace.yaml
- ./external-secret.yaml
- ./service.yaml
- ./statefulset.yaml
|
Apply the Configuration
Apply everything using Kustomize:
1
| kustomize build . | kubectl apply -f -
|
Verify:
1
2
3
4
5
6
7
8
9
10
11
12
13
| $ kubectl get all -n mariadb
NAME READY STATUS RESTARTS AGE
pod/mariadb-app-7c9f6d4d8-gv67k 1/1 Running 0 21h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mariadb-app ClusterIP 10.43.3.189 <none> 3306/TCP 22h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mariadb-app 1/1 1 1 22h
NAME DESIRED CURRENT READY AGE
replicaset.apps/mariadb-app-7c9f6d4d8 1 1 1 22h
|
You should see a running pod, service, and associated deployment.
Deploy MariaDB using Helm + Helmfile (optional)
If you prefer Helm releases managed as code, you can use Helmfile. I have a public Helm charts repo for common things here that we can use.
Create a helmfile.yaml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # helmfile.yaml
---
helmDefaults:
createNamespace: true
wait: true
repositories:
- name: mycharts
url: https://etowett.github.io/helm-charts
releases:
- name: mariadb
namespace: mariadb
chart: mycharts/app
version: "1.1.1"
values:
- ./values.yaml
|
Then create a values file - values.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| replicaCount: 1
image:
repository: mariadb
pullPolicy: IfNotPresent
tag: 11.7
serviceAccount:
create: true
service:
type: ClusterIP
name: mariadb
port: 3306
resources:
limits:
cpu: 1000m
memory: 1024Mi
requests:
cpu: 50m
memory: 64Mi
pvc:
create: true
claimName: mariadb-data-vol
size: 10Gi
volumeMounts:
- name: mariadb-data-vol
mountPath: /var/lib/mysql
volumes:
- name: mariadb-data-vol
persistentVolumeClaim:
claimName: mariadb-data-vol
externalSecrets:
- name: mariadb-credentials
refreshInterval: 5m
secretStoreRefName: vault-backend
targetName: mariadb-credentials
dataKey: infra/mariadb-credentials
secretEnv:
MARIADB_ROOT_PASSWORD:
name: mariadb-credentials
key: root-password
|
Then apply
Validate with:
1
2
3
4
| $ helm ls -n mariadb
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
mariadb mariadb 3 2025-05-05 11:27:54.825897 +0300 EAT deployed app-1.1.1 1.0.0
|
Final Thoughts
Deploying MariaDB in Kubernetes using Helm and Kustomize provides:
- Declarative configuration
- Secure secret management
- Reusable environments
Whether you’re deploying for development or production, this setup ensures consistency, flexibility, and security.