Running MySQL on Kubernetes: Storage, Access, and Backup Boundaries
Learn the essentials of running MySQL on Kubernetes, including StatefulSets, persistent storage, Services, and operational tradeoffs.
Running a single MySQL instance is one of the fastest ways to make Kubernetes storage and service concepts feel real. It also exposes a hard truth early: databases are not just another container. They depend on persistent data, careful startup behavior, and predictable access paths.
Scope of this exercise
Use this exercise to practice:
- learning how a database uses PVC-backed storage
- understanding why Service and readiness matter even for one instance
- practicing backup, restore, and safe local access patterns
It is a good learning step before you move into replication, Helm charts, or more production-like stateful layouts.
A realistic mental model
For a single-instance MySQL on Kubernetes, the minimum useful stack is:
- one MySQL Pod
- one PVC for durable data
- one Service for stable in-cluster access
- one Secret for credentials
That already connects this page to:
- Kubernetes PV and PVC: Binding, Reclaim Policy, and Storage Debugging
- Kubernetes StorageClass: Dynamic Provisioning, Defaults, and Storage Boundaries
- Kubernetes StatefulSet: Stable Identity, Ordered Rollouts, and Storage
- Kubernetes Service: Stable Entry Points, Endpoints, and Traffic Paths
What this creates
- a namespace for the database
- a Secret for credentials
- a PVC for storage
- a single MySQL workload
- a ClusterIP Service for internal access
Create a namespace
kubectl create namespace db
Keeping the database in its own namespace makes policy, access, and cleanup easier to reason about.
Store credentials in a Secret
read -rsp 'MySQL root password: ' MYSQL_ROOT_PASSWORD; echo
kubectl -n db create secret generic mysql-auth \
--from-literal=MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD"
unset MYSQL_ROOT_PASSWORD
This avoids placing the password directly in shell history, but it still exists briefly in a shell variable and then in a Kubernetes Secret. Applications should not use root credentials; inject workload credentials through an external secrets system or a controlled release process.
Minimal resource set
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: db
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: db
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-auth
key: MYSQL_ROOT_PASSWORD
ports:
- containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumes:
- name: data
persistentVolumeClaim:
claimName: mysql-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: db
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
Apply it:
kubectl apply -f mysql.yaml
Why this example still uses Deployment
For one instance, Deployment is acceptable as a learning step because it keeps the example short.
But the important lesson is that this is a stepping stone, not the long-term recommendation. As soon as identity, restart guarantees, or scaling semantics matter more, StatefulSet becomes the better fit.
Verify Pod, PVC, and Service
kubectl get pods -n db
kubectl get pvc -n db
kubectl get svc -n db
If the Pod is Pending, the PVC or StorageClass chain is often the first place to inspect.
Use a temporary client Pod
kubectl run -it --rm mysql-client \
--namespace db \
--image=mysql:8.0 \
--command -- bash
Inside the client:
mysql -h mysql.db.svc.cluster.local -u root -p
Then create a test database and row:
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
CREATE TABLE items (id INT PRIMARY KEY, name VARCHAR(64));
INSERT INTO items VALUES (1, 'hello');
This confirms storage, service discovery, and authentication all work together.
Readiness matters even for databases
MySQL can take time to initialize or recover. If readiness is too aggressive, clients may hit a database that is technically running but not truly ready.
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
initialDelaySeconds: 10
periodSeconds: 5
This connects directly to Kubernetes Probes: Liveness, Readiness, and Startup Protection: databases are one of the easiest places to see why “Running” is not the same as “Ready”.
Storage choices matter immediately
If your cluster has multiple StorageClasses, the PVC should pick one intentionally.
spec:
storageClassName: fast-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
Check what exists first:
kubectl get storageclass
If you want the full storage chain, read:
- Kubernetes PV and PVC: Binding, Reclaim Policy, and Storage Debugging
- Kubernetes StorageClass: Dynamic Provisioning, Defaults, and Storage Boundaries
Backup is part of the example, not an optional afterthought
Even for a single MySQL instance, define a backup path early:
kubectl exec -n db deploy/mysql -- \
mysqldump -u root -p demo > backup.sql
And test restore too:
kubectl exec -n db deploy/mysql -- \
mysql -u root -p demo < backup.sql
PVC is persistence. Backup is recovery. Do not confuse the two.
When to move to StatefulSet
You should move beyond this simple pattern when:
- stable network identity per replica matters
- you want per-replica PVC creation
- restart ordering matters
- you are planning replication
That is where Kubernetes StatefulSet: Stable Identity, Ordered Rollouts, and Storage and MySQL Source and Replicas on Kubernetes: Status, Routing, and Failover become the next natural steps.
Three single-instance MySQL decisions
Q: Is a single MySQL Deployment production-ready? A: Usually no. It is a learning or lightweight environment pattern, not a serious availability design.
Q: Why does MySQL often feel harder than a normal web container? A: Because storage, initialization, recovery, and readiness timing matter much more than they do for stateless services.
Q: What should I check first if MySQL is unreachable? A: Check Pod readiness, then Service endpoints, then credentials, then storage and logs.
Add replication and backup next
- Continue with Kubernetes StatefulSet: Stable Identity, Ordered Rollouts, and Storage for the controller that better fits stateful workloads.
- Read MySQL Source and Replicas on Kubernetes: Status, Routing, and Failover if you want read replicas and topology concerns.
- For storage details, revisit Kubernetes PV and PVC: Binding, Reclaim Policy, and Storage Debugging and Kubernetes StorageClass: Dynamic Provisioning, Defaults, and Storage Boundaries.
Data checks before rollout
This example is useful because it makes the database problem small enough to see clearly. One Pod, one PVC, one Service, one Secret - and already you can feel why stateful workloads demand more care than stateless ones.