CFN Cloud
2025-10-07

Deploying MySQL on Kubernetes: Stateful Basics, Storage, and Services

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.

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.md
  • kubernetes-quickstart-storageclass.md
  • kubernetes-quickstart-statefulset.md
  • kubernetes-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.md
  • kubernetes-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.md for the controller that better fits stateful workloads.
  • Read kubernetes-quickstart-mysql-replication.md if you want read replicas and topology concerns.
  • For storage details, revisit kubernetes-quickstart-pv-pvc.md and kubernetes-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.

References