MySQL Helm Chart
A simple, standalone MySQL Helm chart
docker.io/mysql:8.4.2Add the repository to your Helm configuration:
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm repo update
Then install using the repository:
# Basic installation
helm install my-mysql mysql/mysql
# With custom values
helm install my-mysql mysql/mysql \
--set auth.rootPassword=mypassword \
--set auth.database=mydb \
--set primary.persistence.size=50Gi
# Using a values file
helm install my-mysql mysql/mysql -f my-values.yaml
Clone the repository and install directly:
git clone https://github.com/comet-ml/comet-mysql-helm.git
cd comet-mysql-helm
helm install my-mysql ./
To use this chart as a dependency in another Helm chart, add it to your Chart.yaml:
dependencies:
- name: mysql
version: "1.0.7"
repository: "https://comet-ml.github.io/comet-mysql-helm/"
condition: mysql.enabled
Then run:
helm repo add mysql https://comet-ml.github.io/comet-mysql-helm/
helm dependency update
helm install my-app .
See the examples/ directory for complete example configurations:
simple-values.yamlexisting-pvc-values.yamlextra-args-values.yaml (command-line arguments)initdb-scripts-values.yaml (SQL scripts), init-job-mixed-values.yaml, init-job-secretref-values.yamlpdb-values.yaml (Pod Disruption Budget)network-policy-values.yaml (Network Policy), tls-values.yaml (TLS/SSL)backup-aws-s3-values.yaml, backup-aws-iam-role-values.yaml, backup-minio-values.yamlrestore-aws-s3-values.yaml, restore-aws-iam-role-values.yaml, restore-minio-values.yamlcomplete-setup-values.yamlcommon-labels-values.yamlThe chart supports two methods for database initialization:
SQL scripts that run automatically on first initialization (Bitnami-style):
initdbScripts:
createdb.sql: |-
CREATE DATABASE IF NOT EXISTS myapp
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;
CREATE USER IF NOT EXISTS 'myapp'@'%' IDENTIFIED BY 'myapp_password';
GRANT ALL ON `myapp`.* TO 'myapp'@'%';
FLUSH PRIVILEGES;
Characteristics:
/docker-entrypoint-initdb.d (MySQL standard)Flexible init job that runs after MySQL is ready:
initJob:
enabled: true
databases:
- name: myapp
username: myapp
password: myapp_password # or use passwordSecretRef
- name: production
username: produser
passwordSecretRef:
secretName: prod-db-secret
secretKey: password
Characteristics:
You can use both methods together - initdbScripts run first, then initJob runs after.
initJob:
enabled: true
databases:
- name: myapp
username: myapp
password: myapp_password # or use passwordSecretRef
- name: production
username: produser
passwordSecretRef:
secretName: prod-db-secret
secretKey: password
You can pass additional command-line arguments to MySQL in two ways:
primary.extraFlags (Appends to Configuration)The extraFlags property automatically converts command-line flags to MySQL configuration format and appends them to the existing configuration (default or custom):
primary:
extraFlags: "--max-connections=500 --max-allowed-packet=64M --log-bin-trust-function-creators=1 --thread-stack=256K"
Benefits:
my.cnf formatprimary.configuration)You can provide a complete MySQL configuration that replaces the default:
primary:
configuration: |-
[mysqld]
authentication_policy='* ,,'
skip-name-resolve
port=3306
datadir=/var/lib/mysql
max-connections=500
max-allowed-packet=64M
log-bin-trust-function-creators=1
thread-stack=256K
Use cases:
Note:
primary.extraFlags appends flags to the existing configuration (default or custom)primary.configuration completely replaces the default configurationprimary.configuration provides the base config, and primary.extraFlags adds additional flags on topSee examples/extra-args-values.yaml for a complete example.
Schedule regular backups to S3-compatible storage (AWS S3, MinIO, etc.):
backup:
enabled: true
schedule: "0 2 * * *" # Daily at 2 AM
storage:
bucket: "my-backups"
region: "us-east-1"
prefix: "mysql-backups"
endpoint: "" # Empty for AWS S3, or "http://minio:9000" for MinIO
existingSecret: "aws-s3-credentials" # Optional - use IAM role if empty
retention: 7
databases: [] # Empty = all databases
One-time restore operation:
restore:
enabled: true
backupFile: "mysql_backup_20250130_020000.sql.gz"
storage:
bucket: "my-backups"
region: "us-east-1"
prefix: "mysql-backups"
existingSecret: "aws-s3-credentials"
Protect MySQL from voluntary disruptions (node drains, pod evictions):
podDisruptionBudget:
enabled: true
minAvailable: 1 # Keep at least 1 pod available (for single replica)
# OR
# maxUnavailable: 0 # Prevent any disruption
Restrict network traffic to/from MySQL pods:
networkPolicy:
enabled: true
allowExternal: true # Allow all pods in namespace
# OR restrict to specific namespaces/pods:
# allowExternal: false
# allowedNamespaces:
# - matchLabels:
# name: production
# allowedPods:
# - matchLabels:
# app: myapp
mysql -h <release-name>-mysql -u root -p
kubectl port-forward svc/<release-name>-mysql 3306:3306
mysql -h 127.0.0.1 -P 3306 -u root -p
helm repo update
helm upgrade my-mysql mysql/mysql -f values.yaml
helm upgrade my-mysql ./ -f values.yaml
When migrating from Bitnami MySQL chart to this custom MySQL chart:
primary:
dataDir: /bitnami/mysql/data
kubectl delete statefulset <release-name>-mysql --cascade=orphan
This allows Helm to recreate the StatefulSet with the new chart while reusing the existing PVC and data.
Note: The volume will be mounted at /bitnami/mysql (base folder), and MySQL will use /bitnami/mysql/data as its datadir, matching Bitnami’s structure.
helm uninstall my-mysql
Note: This removes all Kubernetes components but does not delete PVCs by default. To delete PVCs:
kubectl delete pvc -l app.kubernetes.io/name=mysql
kubectl get pods -l app.kubernetes.io/name=mysql
kubectl describe pod <pod-name>
kubectl logs <pod-name>
kubectl get pvc
kubectl describe pvc data-<release-name>-mysql-0
kubectl exec -it <pod-name> -- /bin/bash
┌─────────────────────────────────────┐
│ Service (ClusterIP) │
│ <release-name>-mysql:3306 │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ StatefulSet (Primary) │
│ <release-name>-mysql-0 │
│ - MySQL 8.4.2 (Official Image) │
│ - Port 3306 │
│ - Health Checks │
│ - Resource Limits │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ PersistentVolumeClaim │
│ data-<release-name>-mysql-0 │
│ - /var/lib/mysql │
│ - Size: 20Gi (default) │
│ - Storage Class: configurable │
└─────────────────────────────────────┘
| Key | Type | Default | Description |
|---|---|---|---|
| architecture.mode | string | "standalone" |
|
| auth.database | string | "my_database" |
|
| auth.existingSecret | string | "" |
|
| auth.password | string | "" |
|
| auth.rootPassword | string | "changeme" |
|
| auth.username | string | "" |
|
| backup.databases | list | [] |
|
| backup.enabled | bool | false |
|
| backup.nodeSelector | object | {} |
|
| backup.resources.limits.cpu | string | "500m" |
|
| backup.resources.limits.memory | string | "512Mi" |
|
| backup.resources.requests.cpu | string | "250m" |
|
| backup.resources.requests.memory | string | "256Mi" |
|
| backup.retention | int | 7 |
|
| backup.schedule | string | "0 2 * * *" |
|
| backup.serviceAccountName | string | "" |
|
| backup.storage.bucket | string | "" |
|
| backup.storage.endpoint | string | "" |
|
| backup.storage.existingSecret | string | "" |
|
| backup.storage.prefix | string | "mysql-backups" |
|
| backup.storage.region | string | "us-east-1" |
|
| backup.tolerations | list | [] |
|
| containerSecurityContext.enabled | bool | true |
|
| containerSecurityContext.runAsNonRoot | bool | false |
|
| containerSecurityContext.runAsUser | string | "" |
|
| fullnameOverride | string | "" |
|
| global.commonLabels | object | {} |
|
| global.imageRegistry | string | "" |
|
| global.storageClass | string | "" |
|
| image.pullPolicy | string | "IfNotPresent" |
|
| image.pullSecrets | list | [] |
|
| image.registry | string | "docker.io" |
|
| image.repository | string | "mysql" |
|
| image.tag | string | "8.4.2" |
|
| initJob.annotations.”helm.sh/hook” | string | "post-install,post-upgrade" |
|
| initJob.annotations.”helm.sh/hook-delete-policy” | string | "before-hook-creation,hook-succeeded" |
|
| initJob.annotations.”helm.sh/hook-weight” | string | "5" |
|
| initJob.databases | list | [] |
|
| initJob.enabled | bool | false |
|
| initJob.resources.limits.cpu | string | "500m" |
|
| initJob.resources.limits.memory | string | "256Mi" |
|
| initJob.resources.requests.cpu | string | "100m" |
|
| initJob.resources.requests.memory | string | "128Mi" |
|
| initdbScripts | object | {} |
|
| metrics.containerPort | int | 9104 |
|
| metrics.enabled | bool | false |
|
| metrics.extraArgs | list | [] |
|
| metrics.image.pullPolicy | string | "IfNotPresent" |
|
| metrics.image.registry | string | "docker.io" |
|
| metrics.image.repository | string | "prom/mysqld-exporter" |
|
| metrics.image.tag | string | "v0.16.0" |
|
| metrics.prometheusRule.enabled | bool | false |
|
| metrics.prometheusRule.labels | object | {} |
|
| metrics.prometheusRule.namespace | string | "" |
|
| metrics.prometheusRule.rules | list | [] |
|
| metrics.resources.limits.cpu | string | "100m" |
|
| metrics.resources.limits.memory | string | "128Mi" |
|
| metrics.resources.requests.cpu | string | "50m" |
|
| metrics.resources.requests.memory | string | "64Mi" |
|
| metrics.serviceMonitor.annotations | object | {} |
|
| metrics.serviceMonitor.enabled | bool | false |
|
| metrics.serviceMonitor.interval | string | "30s" |
|
| metrics.serviceMonitor.labels | object | {} |
|
| metrics.serviceMonitor.metricRelabelings | list | [] |
|
| metrics.serviceMonitor.namespace | string | "" |
|
| metrics.serviceMonitor.relabelings | list | [] |
|
| metrics.serviceMonitor.scrapeTimeout | string | "10s" |
|
| nameOverride | string | "" |
|
| networkPolicy.allowExternal | bool | false |
|
| networkPolicy.allowedNamespaces | list | [] |
|
| networkPolicy.allowedPods | list | [] |
|
| networkPolicy.enabled | bool | false |
|
| networkPolicy.extraEgress | list | [] |
|
| networkPolicy.extraIngress | list | [] |
|
| podDisruptionBudget.enabled | bool | false |
|
| podDisruptionBudget.maxUnavailable | int | 1 |
|
| podDisruptionBudget.minAvailable | string | "" |
|
| podSecurityContext.enabled | bool | true |
|
| podSecurityContext.fsGroup | int | 999 |
|
| primary.affinity | object | {} |
|
| primary.configuration | string | "[mysqld]\nauthentication_policy='* ,,'\nskip-name-resolve\nexplicit_defaults_for_timestamp\nport=3306\ndatadir=/var/lib/mysql\nsocket=/var/run/mysqld/mysqld.sock\npid-file=/var/run/mysqld/mysqld.pid\nmax_allowed_packet=16M\nbind-address=0.0.0.0\ncharacter-set-server=utf8mb4\ncollation-server=utf8mb4_unicode_ci\nslow_query_log=0\nlong_query_time=10.0" |
|
| primary.dataDir | string | "/var/lib/mysql" |
|
| primary.existingConfigmap | string | "" |
|
| primary.extraEnvVars | list | [] |
|
| primary.extraFlags | string | "" |
|
| primary.nodeSelector | object | {} |
|
| primary.persistence.accessModes[0] | string | "ReadWriteOnce" |
|
| primary.persistence.annotations | object | {} |
|
| primary.persistence.enabled | bool | true |
|
| primary.persistence.existingClaim | string | "" |
|
| primary.persistence.selector | object | {} |
|
| primary.persistence.size | string | "20Gi" |
|
| primary.persistence.storageClass | string | "" |
|
| primary.podAnnotations | object | {} |
|
| primary.podLabels | object | {} |
|
| primary.resources.limits.cpu | string | "2000m" |
|
| primary.resources.limits.memory | string | "2Gi" |
|
| primary.resources.requests.cpu | string | "500m" |
|
| primary.resources.requests.memory | string | "512Mi" |
|
| primary.tolerations | list | [] |
|
| restore.backupFile | string | "" |
|
| restore.enabled | bool | false |
|
| restore.nodeSelector | object | {} |
|
| restore.resources.limits.cpu | string | "1000m" |
|
| restore.resources.limits.memory | string | "1Gi" |
|
| restore.resources.requests.cpu | string | "500m" |
|
| restore.resources.requests.memory | string | "512Mi" |
|
| restore.serviceAccountName | string | "" |
|
| restore.storage.bucket | string | "" |
|
| restore.storage.endpoint | string | "" |
|
| restore.storage.existingSecret | string | "" |
|
| restore.storage.prefix | string | "mysql-backups" |
|
| restore.storage.region | string | "us-east-1" |
|
| restore.tolerations | list | [] |
|
| service.annotations | object | {} |
|
| service.clusterIP | string | "" |
|
| service.loadBalancerIP | string | "" |
|
| service.loadBalancerSourceRanges | list | [] |
|
| service.nodePort | string | "" |
|
| service.port | int | 3306 |
|
| service.type | string | "ClusterIP" |
|
| serviceAccount.annotations | object | {} |
|
| serviceAccount.create | bool | true |
|
| serviceAccount.name | string | "" |
|
| tls.certCAFilename | string | "ca.crt" |
|
| tls.certFilename | string | "tls.crt" |
|
| tls.certKeyFilename | string | "tls.key" |
|
| tls.enabled | bool | false |
|
| tls.existingSecret | string | "" |
|
| tls.requireSecureTransport | bool | false |
This chart is provided as-is.
Autogenerated from chart metadata using helm-docs v1.14.2