Kubernetes
MySQL
Database
Cloud Native
Container Orchestration

How to connect MySQL running on Kubernetes

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

To connect to MySQL running on Kubernetes, you need a MySQL Deployment or StatefulSet, a Service to expose it, and the correct connection string using the Service's DNS name. Within the cluster, pods connect via mysql-service.namespace.svc.cluster.local:3306. For external access, use kubectl port-forward, a NodePort Service, or a LoadBalancer Service. Store credentials in a Kubernetes Secret and mount them as environment variables.

Deploying MySQL on Kubernetes

Secret for Credentials

yaml
1apiVersion: v1
2kind: Secret
3metadata:
4  name: mysql-secret
5type: Opaque
6data:
7  mysql-root-password: cGFzc3dvcmQxMjM=  # base64 encoded "password123"
8  mysql-database: bXlhcHA=                # base64 encoded "myapp"
bash
# Create base64 values
echo -n "password123" | base64  # cGFzc3dvcmQxMjM=
echo -n "myapp" | base64        # bXlhcHA=

Deployment and Service

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: mysql
5spec:
6  selector:
7    matchLabels:
8      app: mysql
9  template:
10    metadata:
11      labels:
12        app: mysql
13    spec:
14      containers:
15        - name: mysql
16          image: mysql:8.0
17          ports:
18            - containerPort: 3306
19          env:
20            - name: MYSQL_ROOT_PASSWORD
21              valueFrom:
22                secretKeyRef:
23                  name: mysql-secret
24                  key: mysql-root-password
25            - name: MYSQL_DATABASE
26              valueFrom:
27                secretKeyRef:
28                  name: mysql-secret
29                  key: mysql-database
30          volumeMounts:
31            - name: mysql-storage
32              mountPath: /var/lib/mysql
33      volumes:
34        - name: mysql-storage
35          persistentVolumeClaim:
36            claimName: mysql-pvc
37---
38apiVersion: v1
39kind: Service
40metadata:
41  name: mysql-service
42spec:
43  selector:
44    app: mysql
45  ports:
46    - port: 3306
47      targetPort: 3306
48  type: ClusterIP

PersistentVolumeClaim

yaml
1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4  name: mysql-pvc
5spec:
6  accessModes:
7    - ReadWriteOnce
8  resources:
9    requests:
10      storage: 10Gi

Connecting from Inside the Cluster

Pods within the same Kubernetes cluster connect using the Service's DNS name:

python
1# Python application in the same namespace
2import mysql.connector
3
4connection = mysql.connector.connect(
5    host="mysql-service",      # Service name
6    port=3306,
7    user="root",
8    password="password123",
9    database="myapp"
10)
java
1// Java / Spring Boot application.properties
2spring.datasource.url=jdbc:mysql://mysql-service:3306/myapp
3spring.datasource.username=root
4spring.datasource.password=password123

Full DNS Name

 
mysql-service.default.svc.cluster.local

Format: <service-name>.<namespace>.svc.cluster.local

If the application is in a different namespace:

python
1connection = mysql.connector.connect(
2    host="mysql-service.database-namespace.svc.cluster.local",
3    port=3306,
4    user="root",
5    password="password123",
6    database="myapp"
7)

Connecting from Outside the Cluster

Method 1: kubectl port-forward (Development)

bash
1# Forward local port 3306 to the MySQL pod
2kubectl port-forward svc/mysql-service 3306:3306
3
4# Now connect locally
5mysql -h 127.0.0.1 -P 3306 -u root -p

This is for development only — the tunnel stops when you close the terminal.

Method 2: NodePort Service

yaml
1apiVersion: v1
2kind: Service
3metadata:
4  name: mysql-nodeport
5spec:
6  type: NodePort
7  selector:
8    app: mysql
9  ports:
10    - port: 3306
11      targetPort: 3306
12      nodePort: 30306  # Accessible on any node's IP at port 30306
bash
# Connect using any node's IP
mysql -h <node-ip> -P 30306 -u root -p

Method 3: LoadBalancer Service (Cloud)

yaml
1apiVersion: v1
2kind: Service
3metadata:
4  name: mysql-lb
5spec:
6  type: LoadBalancer
7  selector:
8    app: mysql
9  ports:
10    - port: 3306
11      targetPort: 3306
bash
1# Get the external IP
2kubectl get svc mysql-lb
3# NAME       TYPE           EXTERNAL-IP    PORT(S)
4# mysql-lb   LoadBalancer   203.0.113.50   3306:31234/TCP
5
6mysql -h 203.0.113.50 -P 3306 -u root -p

Using a StatefulSet (Production)

For production MySQL deployments, use a StatefulSet with stable network identities:

yaml
1apiVersion: apps/v1
2kind: StatefulSet
3metadata:
4  name: mysql
5spec:
6  serviceName: mysql
7  replicas: 1
8  selector:
9    matchLabels:
10      app: mysql
11  template:
12    metadata:
13      labels:
14        app: mysql
15    spec:
16      containers:
17        - name: mysql
18          image: mysql:8.0
19          ports:
20            - containerPort: 3306
21          env:
22            - name: MYSQL_ROOT_PASSWORD
23              valueFrom:
24                secretKeyRef:
25                  name: mysql-secret
26                  key: mysql-root-password
27          volumeMounts:
28            - name: mysql-data
29              mountPath: /var/lib/mysql
30  volumeClaimTemplates:
31    - metadata:
32        name: mysql-data
33      spec:
34        accessModes: ["ReadWriteOnce"]
35        resources:
36          requests:
37            storage: 20Gi

StatefulSet pods get predictable DNS names: mysql-0.mysql.default.svc.cluster.local.

Running a MySQL Client Pod

bash
1# Run a temporary pod with the MySQL client
2kubectl run mysql-client --rm -it --image=mysql:8.0 -- \
3  mysql -h mysql-service -u root -ppassword123
4
5# Or use a one-liner to test connectivity
6kubectl run test-mysql --rm -it --image=mysql:8.0 -- \
7  mysql -h mysql-service -u root -ppassword123 -e "SHOW DATABASES;"

Common Pitfalls

  • No PersistentVolumeClaim: Without persistent storage, MySQL data is lost when the pod restarts. Always attach a PVC with ReadWriteOnce access mode for MySQL's data directory (/var/lib/mysql).
  • Using Deployment instead of StatefulSet: Deployments do not guarantee stable network identities or ordered startup/shutdown. For production MySQL, use a StatefulSet which provides stable pod names and persistent volume binding.
  • Exposing MySQL externally without security: Using LoadBalancer or NodePort exposes MySQL to the internet. Always restrict access with network policies, firewall rules, or VPN. Never expose root credentials on a public endpoint.
  • Hardcoding passwords in YAML: Store MySQL credentials in Kubernetes Secrets, not in plain text in Deployment manifests. Reference secrets with secretKeyRef in environment variables.
  • DNS resolution failures: If the application pod cannot resolve mysql-service, check that the Service exists in the same namespace or use the full DNS name. Run kubectl get svc to verify the Service is created and kubectl exec into the app pod to test DNS with nslookup mysql-service.

Summary

  • Deploy MySQL as a Deployment or StatefulSet with a PVC for persistent storage
  • Create a ClusterIP Service for internal cluster access
  • Connect from within the cluster using mysql-service:3306 (or full DNS name for cross-namespace)
  • Use kubectl port-forward for development access from outside the cluster
  • Use NodePort or LoadBalancer for external access (with proper security)
  • Store credentials in Kubernetes Secrets and reference them in pod environment variables

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.