Skip to content

MySQL

Deploy MySQL on Kubernetes using the official mysql image. The chart supports standalone and replication architectures, S3-compatible backups, Prometheus metrics, TLS, dual-stack Services, External Secrets Operator, and production hardening controls that can be enabled without changing the development-friendly defaults.

Key Features

  • Standalone and source-replica - Single instance or source with read replicas.
  • Official MySQL image - Uses docker.io/library/mysql:9.7.0 by default.
  • Deterministic credentials - Inline values for development or existing Secrets/External Secrets for production.
  • TLS - Server TLS, optional secure transport requirement, internal client TLS, and private-key permission normalization.
  • NetworkPolicy - Optional ingress rules plus explicit egress for DNS, same-namespace MySQL, HTTPS, and custom targets.
  • S3 backup - Scheduled mysqldump CronJobs with existing Secret support for object storage credentials.
  • Observability - mysqld-exporter sidecar with optional ServiceMonitor.
  • Dual-stack Services - Optional ipFamilyPolicy and ipFamilies across chart-managed Services.
  • Least privilege - Non-root containers, dropped capabilities, and opt-in ServiceAccount token mounting.

Architecture

Standalone

Single MySQL pod with persistent storage, metrics, optional TLS, and optional backup CronJob.

Application client TCP:3306 MySQL StatefulSet (1 pod) source PVC (data) Backup CronJob mysqldump → S3

Source-Replica

Writable source pod with binary log retention and read-only replicas behind a replica Service.

Application read/write Source read + write pod-0 binlog Replica read-only pod-1 PVC (data) PVC (data) Backup CronJob → S3

Installation

HTTPS repository:

helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install my-mysql helmforge/mysql -f values.yaml

OCI registry:

helm install my-mysql oci://ghcr.io/helmforgedev/helm/mysql -f values.yaml
Defaults are for development and smoke tests

The chart can run with generated credentials and small storage defaults, but production installs should pin credentials in an existing Secret or External Secrets backend, size PVCs/resources explicitly, and enable backup before storing critical data.

Deployment Examples

# values.yaml - standalone development or small workload
architecture: standalone

auth:
  rootPassword: 'my-secret-password'
  database: myapp
  username: myuser
  password: 'user-password'

standalone:
  persistence:
    size: 20Gi

metrics:
  enabled: true
  serviceMonitor:
    enabled: true
# values.yaml - source with two read replicas
architecture: replication

auth:
  rootPassword: 'my-secret-password'
  database: myapp
  username: myuser
  password: 'user-password'
  replicationPassword: 'repl-password'

replication:
  source:
    persistence:
      size: 20Gi
  readReplicas:
    replicaCount: 2
    persistence:
      size: 20Gi
# values.yaml - backup credentials from an existing Secret
architecture: standalone

auth:
  existingSecret: mysql-auth
  database: myapp
  username: myapp

standalone:
  persistence:
    size: 20Gi

backup:
  enabled: true
  schedule: '0 3 * * *'
  s3:
    endpoint: https://s3.amazonaws.com
    bucket: my-mysql-backups
    prefix: mysql
    existingSecret: mysql-backup
# values.yaml - production baseline with replication, TLS, backup, and policies
architecture: replication

auth:
  existingSecret: mysql-auth
  database: myapp
  username: myapp

replication:
  source:
    persistence:
      size: 100Gi
    resources:
      requests:
        cpu: 500m
        memory: 1Gi
      limits:
        cpu: '2'
        memory: 2Gi
  readReplicas:
    replicaCount: 2
    persistence:
      size: 100Gi
    resources:
      requests:
        cpu: 250m
        memory: 768Mi
      limits:
        cpu: '1'
        memory: 1536Mi

tls:
  enabled: true
  existingSecret: mysql-tls
  requireSecureTransport: true
  volumePermissions:
    enabled: true
  client:
    enabled: true
    sslMode: REQUIRED

metrics:
  enabled: true
  serviceMonitor:
    enabled: true

networkPolicy:
  enabled: true
  egress:
    enabled: true
    allowDNS: true
    allowSameNamespaceMySQL: true
    allowHTTPS: true

serviceAccount:
  create: true
  automountServiceAccountToken: false

backup:
  enabled: true
  s3:
    endpoint: https://s3.example.com
    bucket: mysql-backups
    existingSecret: mysql-backup
# values.yaml - render ExternalSecret resources for existing ESO clusters
auth:
  existingSecret: mysql-auth

tls:
  enabled: true
  existingSecret: mysql-tls

backup:
  enabled: true
  s3:
    existingSecret: mysql-backup

externalSecrets:
  enabled: true
  apiVersion: external-secrets.io/v1
  secretStoreRef:
    name: platform-secrets
    kind: ClusterSecretStore
  auth:
    enabled: true
    targetName: mysql-auth
    rootPasswordRemoteRef:
      key: prod/mysql
      property: root-password
    userPasswordRemoteRef:
      key: prod/mysql
      property: user-password
    replicationPasswordRemoteRef:
      key: prod/mysql
      property: replication-password
  tls:
    enabled: true
    targetName: mysql-tls
    caRemoteRef:
      key: prod/mysql-tls
      property: ca.crt
    certRemoteRef:
      key: prod/mysql-tls
      property: tls.crt
    keyRemoteRef:
      key: prod/mysql-tls
      property: tls.key
  backup:
    enabled: true
    targetName: mysql-backup
    accessKeyRemoteRef:
      key: prod/mysql-backup
      property: access-key
    secretKeyRemoteRef:
      key: prod/mysql-backup
      property: secret-key
External Secrets Operator is optional

The chart does not install External Secrets Operator and does not create a SecretStore or ClusterSecretStore. Enable this only when the operator and referenced store already exist.

Production Path

Keep defaults for local development, CI smoke tests, and disposable environments. For production, make these decisions explicitly:

  • Store auth.*Password, TLS material, and backup credentials in existing Secrets or External Secrets.
  • Size replication.source.persistence, replication.readReplicas.persistence, and resources for the actual workload.
  • Enable tls.requireSecureTransport when clients should be forced to use encrypted TCP connections.
  • Enable metrics and backup before accepting durable workload traffic.
  • Enable NetworkPolicy only after modelling required ingress and egress paths.

TLS private key permissions

Some MySQL versions reject private keys projected from Secrets when mode or ownership is too permissive for the runtime user. tls.volumePermissions.enabled copies TLS material into an emptyDir, normalizes ownership for UID/GID 999, and restricts the private key before mysqld starts.

NetworkPolicy egress

networkPolicy.egress.enabled=false preserves default CNI egress behavior. When enabled, use the structured switches for DNS, same-namespace MySQL replication/internal clients, HTTPS to object storage or external secret backends, and networkPolicy.egress.extraTo for platform-specific destinations.

ServiceAccount token mount

serviceAccount.automountServiceAccountToken defaults to false. Keep it disabled unless a sidecar, extension, or platform integration inside the pod needs Kubernetes API credentials.

Configuration Reference

Core and Image

ParameterTypeDefaultDescription
architecturestringstandaloneDeployment architecture: standalone or replication.
image.repositorystringdocker.io/library/mysqlMySQL runtime image repository.
image.tagstring"9.7.0"MySQL runtime image tag.
clusterDomainstringcluster.localKubernetes cluster DNS domain.
commonLabelsobject{}Extra labels added to all resources.

Authentication

ParameterTypeDefaultDescription
auth.rootPasswordstring""Root password when no existing Secret is used.
auth.databasestringappApplication database created on first bootstrap.
auth.usernamestringappApplication user created on first bootstrap.
auth.passwordstring""Application user password when no existing Secret is used.
auth.replicationUsernamestringreplicatorReplication user used by read replicas.
auth.replicationPasswordstring""Replication password when no existing Secret is used.
auth.existingSecretstring""Existing Secret containing MySQL passwords.
auth.existingSecretRootPasswordKeystringmysql-root-passwordSecret key for the root password.
auth.existingSecretUserPasswordKeystringmysql-user-passwordSecret key for the application user password.
auth.existingSecretReplicationPasswordKeystringmysql-replication-passwordSecret key for the replication password.

Replication, Persistence, and Backup

ParameterTypeDefaultDescription
standalone.persistence.sizestring8GiPVC size for standalone mode.
replication.source.persistence.sizestring20GiPVC size for the source pod.
replication.readReplicas.replicaCountinteger2Number of read replica pods.
replication.readReplicas.persistence.sizestring20GiPVC size for each read replica.
replication.binlog.retentionDaysinteger7Binary log retention in whole days.
replication.pdb.enabledbooleantrueEnable a PDB by default in replication mode.
backup.enabledbooleanfalseEnable the built-in backup CronJob.
backup.schedulestring"0 3 * * *"Backup schedule.
backup.s3.existingSecretstring""Existing Secret with S3 access and secret keys.
backup.database.mysqldumpArgsstringSee chart valuesExtra arguments passed before --all-databases.

TLS, Services, Metrics, and Policies

ParameterTypeDefaultDescription
tls.enabledbooleanfalseEnable MySQL server TLS.
tls.existingSecretstring""Existing Secret containing TLS material.
tls.requireSecureTransportbooleanfalseRequire encrypted TCP client connections.
tls.volumePermissions.enabledbooleanfalseNormalize copied TLS private-key ownership and permissions before MySQL starts.
tls.client.enabledbooleanfalseEnable TLS for chart-managed internal TCP clients.
service.ipFamilyPolicystringomittedService IP family policy: SingleStack, PreferDualStack, or RequireDualStack. Omit for cluster default.
service.ipFamiliesarrayomittedOrdered list of IP families (IPv4, IPv6). Omit for cluster default.
metrics.enabledbooleanfalseEnable mysqld-exporter.
metrics.image.tagstring"v0.17.2"mysqld-exporter image tag.
networkPolicy.enabledbooleanfalseCreate a NetworkPolicy for MySQL pods.
networkPolicy.egress.enabledbooleanfalseAdd explicit egress rules to the NetworkPolicy.
serviceAccount.automountServiceAccountTokenbooleanfalseMount Kubernetes API credentials into workload pods.

External Secrets Operator

ParameterTypeDefaultDescription
externalSecrets.enabledbooleanfalseRender ExternalSecret resources.
externalSecrets.apiVersionstringexternal-secrets.io/v1ExternalSecret API version.
externalSecrets.secretStoreRef.namestring""Existing SecretStore or ClusterSecretStore name.
externalSecrets.auth.enabledbooleanfalseManage the MySQL auth Secret through ESO.
externalSecrets.tls.enabledbooleanfalseManage the MySQL TLS Secret through ESO.
externalSecrets.backup.enabledbooleanfalseManage the backup object-storage Secret through ESO.

Dual-stack Networking

By default, service.ipFamilyPolicy and service.ipFamilies are unset and the cluster default remains authoritative. Set them only when your cluster supports the requested families.

service:
  ipFamilyPolicy: PreferDualStack
  ipFamilies:
    - IPv4
    - IPv6

Use PreferDualStack for mixed environments. RequireDualStack should be reserved for clusters where both IPv4 and IPv6 are guaranteed.

Upgrade Notes

Password persistence

MySQL stores credentials during initial setup. On upgrade, ensure the provided Secret values match the existing data directory or the pod can fail authentication during startup.

  • Switching from standalone to replication requires a planned migration or re-initialization.
  • Backup CronJob changes take effect on the next scheduled run.
  • Read replicas are read-only; write traffic must target the source/client Service.

Common Issues

Pod stuck in CrashLoopBackOff after upgrade

The most common cause is credentials that do not match the existing data directory. Check pod logs and verify the Secret values before deleting PVCs.

Replication lag monitoring

Enable metrics and monitor replica lag. Persistent lag usually means replicas need more CPU, memory, storage IOPS, or a reduced write workload.

More Information