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.
What this quickstart is actually for
This page is for:
- 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-quickstart-pv-pvc.mdkubernetes-quickstart-storageclass.mdkubernetes-quickstart-statefulset.mdkubernetes-quickstart-service.md
What you will build
- 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
kubectl -n db create secret generic mysql-auth \
--from-literal=MYSQL_ROOT_PASSWORD='ChangeMe123!'
For real workloads, avoid using root credentials from the application. Treat root as an operator credential, not an app default.
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-quickstart-probes.md: 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-quickstart-pv-pvc.mdkubernetes-quickstart-storageclass.md
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-quickstart-statefulset.md and kubernetes-quickstart-mysql-replication.md become the next natural steps.
FAQ
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.
Next reading
- Continue with
kubernetes-quickstart-statefulset.mdfor the controller that better fits stateful workloads. - Read
kubernetes-quickstart-mysql-replication.mdif you want read replicas and topology concerns. - For storage details, revisit
kubernetes-quickstart-pv-pvc.mdandkubernetes-quickstart-storageclass.md.
Wrap-up
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.