> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-majid-docs-content-expansion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# MySQL

> Deploy MySQL on Control Plane using the Template Catalog. Covers prerequisite credential secrets, volumes, internal access, the optional phpMyAdmin console, S3/GCS backups, and migrating from template version 1.4.3.

## Overview

MySQL is a widely used open-source relational database management system. This template deploys a single-replica MySQL instance with persistent storage, an optional phpMyAdmin web console, and optional scheduled backups to AWS S3 or GCS.

Database credentials are **not** template values. MySQL reads both the application credentials and the `root` password from two secrets you create before installing, so no password passes through Helm or lands in the release.

<Warning>
  **Template version 1.5.0 is a breaking security change.** The `config` block and `enablePhpMyAdmin` were removed, and an install or upgrade that still sets either one now fails at render instead of silently falling back to a default password. If you are running 1.4.3 or earlier, read [Upgrading From 1.4.3 or Earlier](#upgrading-from-1-4-3-or-earlier) before you touch the release.
</Warning>

<Note>
  MySQL on Control Plane operates as a single-replica deployment. Do not scale up the replica count, as this would result in multiple isolated instances rather than a replicated cluster.
</Note>

### What Gets Created

* **Stateful Workload** — (`RELEASE_NAME-mysql`): a single-replica MySQL container serving TCP on port `3306`, with configurable resources.
* **Volume Set** — (`RELEASE_NAME-mysql-vs`): an `ext4` volume mounted at `/var/lib/mysql` holding all database data, with optional autoscaling.
* **Identity & Policy** — An identity bound to the database and backup workloads, and a policy granting it `reveal` on exactly the two credential secrets you created — nothing else. When backups are enabled, the identity also carries the Cloud Account binding the backup job uses to reach your bucket.
* **phpMyAdmin Workload** *(optional)* — (`RELEASE_NAME-mysql-phpmyadmin`): a serverless browser console on port `80`, created when `phpMyAdmin.enabled: true`. It has no identity and no access to any secret.
* **Backup Cron Workload** *(optional)* — (`RELEASE_NAME-mysql-backup`): a scheduled `mysqldump` to an S3 or GCS bucket, created when `backup.enabled: true`.

The template creates **no credential secret of its own**. Every password lives in the prerequisite secrets described below.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Upgrading From 1.4.3 or Earlier

Template versions up to 1.4.3 took the database credentials as plain Helm values and shipped working defaults for them, and they deployed phpMyAdmin enabled and reachable from the entire internet, with no knob to restrict it short of disabling the console. Version 1.5.0 removes both.

|                         | 1.4.3 and earlier                                          | 1.5.0                                                                                     |
| ----------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Application credentials | `config.user`, `config.password`, `config.db` values       | `credentialsSecretName` → a dictionary secret you create                                  |
| Root password           | `config.rootPassword` value                                | `rootPasswordSecretName` → an opaque secret you create                                    |
| phpMyAdmin              | `enablePhpMyAdmin: true`, public, hardcoded to `0.0.0.0/0` | `phpMyAdmin.enabled: false`, with its own `publicAccess` (off) and `internalAccess` knobs |
| phpMyAdmin image        | `phpmyadmin/phpmyadmin:latest`                             | `phpmyadmin:5.2.3-apache`                                                                 |
| phpMyAdmin credentials  | An identity plus the root password in its environment      | None — no identity, no secret access                                                      |
| Credential secret       | `RELEASE_NAME-mysql-config` held every credential          | Not created — credentials live only in your secrets                                       |

<Warning>
  **Carrying old values forward stops the upgrade.** Both removed keys are rejected at render, so `cpln helm upgrade` fails and your existing release is left untouched and running rather than quietly restarting with a different password:

  ```text theme={null}
  Error: execution error at (mysql/templates/workload-mysql.yaml:1:4): mysql: the `config` block was
  removed in 1.5.0 — database credentials are no longer values. Create a `dictionary` secret with
  `username`, `password` and `database`, an `opaque` secret with the root password, and set
  `credentialsSecretName` and `rootPasswordSecretName` to their names. See Prerequisites in the README.
  ```

  ```text theme={null}
  Error: ... mysql: `enablePhpMyAdmin` was renamed in 1.5.0 — use `phpMyAdmin.enabled` instead. Note
  phpMyAdmin now defaults to OFF, and to internal-only access when enabled
  (`phpMyAdmin.publicAccess.enabled`).
  ```
</Warning>

<Steps>
  <Step title="Read the credentials the database already uses">
    Credentials are written into the data directory the first time the volume is initialized, so an existing database keeps whatever it was created with. Recover them from your current values file, or from the secret the old version created:

    ```bash theme={null}
    cpln secret reveal RELEASE_NAME-mysql-config -o yaml
    ```

    The keys are `user`, `password`, `database` and `rootPassword`.
  </Step>

  <Step title="Create the two prerequisite secrets with those same values">
    Follow [Prerequisites](#prerequisites), using the existing username, password, database name and root password. Using different values here does not change the database — it just leaves the workload unable to authenticate.
  </Step>

  <Step title="Remove the old keys from your values">
    Delete the entire `config` block and `enablePhpMyAdmin`, then set `credentialsSecretName` and `rootPasswordSecretName` to the two secret names. If you were using the console, add `phpMyAdmin.enabled: true` — it is off by default now, and internal-only unless you also set `phpMyAdmin.publicAccess.enabled: true`.
  </Step>

  <Step title="Upgrade, then rotate the passwords">
    After the upgrade succeeds, change any password that came from a 1.4.x default — those defaults were published in the public template repository, so treat them as compromised. Rotate inside MySQL and update the secret to match:

    ```sql theme={null}
    ALTER USER 'root'@'localhost' IDENTIFIED BY 'NEW-STRONG-ROOT-PASSWORD';
    ALTER USER 'root'@'%' IDENTIFIED BY 'NEW-STRONG-ROOT-PASSWORD';
    ALTER USER 'appuser'@'%' IDENTIFIED BY 'NEW-STRONG-PASSWORD';
    ```

    The image creates `root` for both `localhost` and `%`, so rotating root means changing both. Use your own username in place of `appuser`, then update the two secrets so the workload can still authenticate after a restart.
  </Step>
</Steps>

## Prerequisites

**Two secrets must exist before you install.** They are deliberately separate: an application handed `reveal` on the database credentials must not also get `root`. Neither value passes through Helm values, so neither lands in the release. Secrets are org-level, so no GVC flag is involved.

<Steps>
  <Step title="Create the application credentials secret">
    A [dictionary secret](/guides/create-secret/dictionary) holding exactly three keys — `username`, `password` and `database`. MySQL creates this user and this database on first boot, and this is the credential your applications put in their connection strings:

    ```bash theme={null}
    cpln secret create-dictionary --name my-mysql-credentials \
      --entry username=appuser \
      --entry password='YOUR-STRONG-PASSWORD' \
      --entry database=appdb
    ```

    Set `credentialsSecretName` to the name you used.
  </Step>

  <Step title="Create the root password secret">
    An [opaque secret](/guides/create-secret/opaque) with encoding `plain` holding the MySQL `root` password:

    ```bash theme={null}
    printf '%s' 'YOUR-STRONG-ROOT-PASSWORD' | cpln secret create-opaque --name my-mysql-root-password --encoding plain -f -
    ```

    Set `rootPasswordSecretName` to the name you used.
  </Step>

  <Step title="Read either secret back later">
    ```bash theme={null}
    cpln secret reveal my-mysql-credentials -o yaml
    ```
  </Step>
</Steps>

<Warning>
  Create both secrets **before** installing. The template refuses to render when either name is blank, but a name that points at a secret which does not exist installs "successfully" and then wedges: the workload waits on the missing secret and looks broken. Confirm the database actually started with `cpln workload get-deployments RELEASE_NAME-mysql --gvc GVC_NAME` rather than trusting the Helm output.
</Warning>

Backups need a bucket and a Control Plane Cloud Account before they can be enabled — see [Backup Prerequisites](#backup-prerequisites). Nothing else is required.

## Installation

To install, follow the instructions for your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: mysql:9 # any supported MySQL major works, including the backup feature (the backup image dumps whichever database is configured)

# ─── Credentials (prerequisite secrets) ───────────────────────────────────────
# BOTH SECRETS MUST EXIST BEFORE YOU INSTALL — the deployment wedges waiting on a
# secret that does not exist. Neither value passes through Helm values, so neither
# lands in the release. See Prerequisites in the README for the exact commands.

# `dictionary` secret holding exactly three keys: `username`, `password`, `database`.
# This is the credential you put in your applications' connection strings.
credentialsSecretName: my-mysql-credentials

# `opaque` secret (encoding: plain) holding the MySQL root password. Deliberately
# separate, so sharing the application credentials above does not hand out root.
rootPasswordSecretName: my-mysql-root-password

resources:
  minCpu: 100m
  maxCpu: 400m
  minMemory: 128Mi
  maxMemory: 512Mi

timeoutSeconds: 15

internalAccess: # Which workloads may reach MySQL on port 3306
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: [] # only used when type is workload-list
    #- //gvc/GVC_NAME/workload/WORKLOAD_NAME

volumeset:
  capacity: 10 # initial capacity in GiB (minimum is 10)
  autoscaling:
    enabled: false # Set to true to enable autoscaling
    maxCapacity: 100 # Maximum capacity in GiB when autoscaling is enabled
    minFreePercentage: 10 # Minimum free percentage to trigger scaling when autoscaling is enabled
    scalingFactor: 1.2 # Scaling factor to determine how much to scale up when autoscaling is triggered

# ─── phpMyAdmin (optional admin console) ──────────────────────────────────────
phpMyAdmin:
  enabled: false # true deploys a phpMyAdmin console alongside the database
  image: phpmyadmin:5.2.3-apache
  publicAccess:
    enabled: false # true publishes the console, and its login form, to the whole internet
  internalAccess:
    type: same-gvc # options: none, same-gvc, same-org, workload-list
    workloads: [] # only used when type is workload-list
      #- //gvc/GVC_NAME/workload/WORKLOAD_NAME
  resources:
    cpu: 100m
    memory: 128Mi

backup:
  enabled: false
  image: ghcr.io/controlplane-com/backup-images/mysql-backup:1.0.0 # compatible with all MySQL image versions
  schedule: "0 2 * * *"   # daily at 2am UTC

  resources:
    cpu: 100m
    memory: 128Mi

  provider: aws # Options: aws or gcp

  aws:
    bucket: my-backup-bucket
    region: us-east-1
    cloudAccountName: my-backup-cloudaccount
    policyName: my-backup-policy
    prefix: mysql/backups # folder name where your backups will be stored

  gcp:
    bucket: my-backup-bucket
    cloudAccountName: my-backup-cloudaccount
    prefix: mysql/backups # folder name where your backups will be stored
```

### Credentials

* `credentialsSecretName` — Name of the dictionary secret holding `username`, `password` and `database`. MySQL creates that user and that database on first boot.
* `rootPasswordSecretName` — Name of the opaque secret holding the `root` password.

Both secrets must exist before installing — see [Prerequisites](#prerequisites). The workload reads them through `cpln://secret/...` references, so the values appear in neither the Helm release nor the stored workload spec.

<Note>
  These credentials are only applied on first startup when the data directory is empty. Rotating either secret afterwards does not change the stored passwords; change them inside MySQL with `ALTER USER` and update the secret to match.
</Note>

### Resources

* `resources.minCpu` / `resources.minMemory` — Minimum CPU and memory guaranteed to the workload.
* `resources.maxCpu` / `resources.maxMemory` — Maximum CPU and memory the workload can use.
* `timeoutSeconds` — Workload timeout in seconds.

### Storage

* `volumeset.capacity` — Initial volume size in GiB (minimum 10).
* `volumeset.autoscaling.enabled` — Automatically expand the volume as it fills. When enabled:
  * `maxCapacity` — Maximum volume size in GiB.
  * `minFreePercentage` — Trigger a scale-up when free space drops below this percentage.
  * `scalingFactor` — Multiply the current capacity by this factor when scaling up.

### Internal Access

* `internalAccess.type` — Controls which workloads can connect to MySQL on port `3306`:

| Type            | Description                                                     |
| --------------- | --------------------------------------------------------------- |
| `none`          | No internal access allowed                                      |
| `same-gvc`      | Allow access from all workloads in the same GVC                 |
| `same-org`      | Allow access from all workloads in the same organization        |
| `workload-list` | Allow access only from specific workloads listed in `workloads` |

The database is never published to the internet by this template. Reach it from inside the GVC, or from outside through your own proxy.

### phpMyAdmin

* `phpMyAdmin.enabled` — When `true`, deploys a phpMyAdmin workload for browser-based database management. Off by default.
* `phpMyAdmin.image` — Pinned to `phpmyadmin:5.2.3-apache` from the official Docker library repository.
* `phpMyAdmin.publicAccess.enabled` — When `true`, publishes the console, and its login form, to the whole internet. Off by default.
* `phpMyAdmin.internalAccess` — Which workloads may reach the console, using the same four types as the database. This governs the **console only**; the database has its own `internalAccess` knob.
* `phpMyAdmin.resources.cpu` / `phpMyAdmin.resources.memory` — Console resources.

The console holds **no standing credential**: it has no identity, no `reveal` grant, and no password in its environment. It presents a login form, you supply either the application credentials or `root` plus the root password, and it connects to the database as whoever logged in. Compromising the console container therefore yields no password.

<Warning>
  `phpMyAdmin.publicAccess.enabled: true` puts a database console on the public internet, where anyone who reaches it needs only a valid database password to read and write everything. Prefer leaving it off and reaching the console from inside the GVC.
</Warning>

<Note>
  Access changes take up to a couple of minutes to propagate. Enabling public access was measured moving through `421` and `503` before serving `200`, settling in about 34 seconds — a request made immediately after the upgrade is not evidence the knob is broken.
</Note>

### Backup

Set `backup.enabled: true` to enable scheduled database dumps to object storage. The job authenticates as `root` and dumps the database named in your credentials secret; both come from the same two secrets the database uses.

Set `backup.provider` to `aws` or `gcp` and fill in the corresponding section. The `prefix` field controls the folder path within the bucket where backups are stored. See [Backup Prerequisites](#backup-prerequisites) for the bucket, Cloud Account and permissions setup.

## Backup Prerequisites

Only needed when `backup.enabled: true`.

### AWS S3

1. Create an S3 bucket. Set `backup.aws.bucket` and `backup.aws.region` in your values file.

2. If you do not have a Control Plane Cloud Account set up, follow the [Create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) guide. Set `backup.aws.cloudAccountName` to the name of your Cloud Account.

3. Create an IAM policy with the following JSON, replacing `YOUR_BUCKET_NAME`:

<Warning>
  **Version 1.5.1 narrows AWS backup permissions.** This version removes `aws::ReadOnlyAccess` from the backup identity. That AWS managed policy granted read access to **every bucket in your AWS account** and contains no write actions at all, so it was never carrying the backup itself — but it *was* silently supplying any read action your own bucket-scoped policy happened to omit.

  **Update your IAM policy to the full action list below before upgrading.** If it already matches, no action is needed. The identity now carries `cpln-connector` and your bucket-scoped policy only, which is strictly narrower than before. Nothing else changes.
</Warning>

```json theme={null}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:ListBucket",
                "s3:GetObjectVersion",
                "s3:DeleteObjectVersion",
                "s3:GetBucketLocation",
                "s3:AbortMultipartUpload",
                "s3:ListBucketMultipartUploads",
                "s3:ListMultipartUploadParts"
            ],
            "Resource": [
                "arn:aws:s3:::YOUR_BUCKET_NAME",
                "arn:aws:s3:::YOUR_BUCKET_NAME/*"
            ]
        }
    ]
}
```

4. Set `backup.aws.policyName` to the name of the policy created in step 3. The template attaches it to the workload's identity.

### GCS

1. Create a GCS bucket. Set `backup.gcp.bucket` in your values file.

2. If you do not have a Control Plane Cloud Account set up, follow the [Create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) guide. Set `backup.gcp.cloudAccountName` to the name of your Cloud Account.

3. Add the **Storage Admin** role to the GCP service account associated with the Cloud Account. The template binds `roles/storage.objectAdmin` on exactly that bucket.

## Restoring a Backup

Run the following from a client with access to the bucket, using the `root` password from your `rootPasswordSecretName` secret. Dumps carry a `SET @@GLOBAL.GTID_PURGED` statement that must be stripped when loading into a running server:

**AWS S3**

```bash theme={null}
export MYSQL_PWD="ROOT_PASSWORD"

aws s3 cp "s3://BUCKET_NAME/PREFIX/BACKUP_FILE.sql.gz" - \
  | gunzip \
  | sed '/^SET @@GLOBAL.GTID_PURGED/d' \
  | mysql \
      --host=WORKLOAD_NAME \
      --port=3306 \
      --user=root \
      DATABASE_NAME

unset MYSQL_PWD
```

**GCS**

```bash theme={null}
export MYSQL_PWD="ROOT_PASSWORD"

gsutil cp "gs://BUCKET_NAME/PREFIX/BACKUP_FILE.sql.gz" - \
  | gunzip \
  | sed '/^SET @@GLOBAL.GTID_PURGED/d' \
  | mysql \
      --host=WORKLOAD_NAME \
      --port=3306 \
      --user=root \
      DATABASE_NAME

unset MYSQL_PWD
```

## Connecting

| Path                    | Address                                                    | Notes                                                                                                                                                    |
| ----------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Database                | `RELEASE_NAME-mysql.GVC_NAME.cpln.local:3306`              | Subject to `internalAccess.type`.                                                                                                                        |
| phpMyAdmin (internal)   | `http://RELEASE_NAME-mysql-phpmyadmin.GVC_NAME.cpln.local` | Only when `phpMyAdmin.enabled: true`.                                                                                                                    |
| phpMyAdmin (public)     | `https://CANONICAL_ENDPOINT`                               | Only when `phpMyAdmin.publicAccess.enabled: true`. Read it from `status.canonicalEndpoint` in `cpln workload get RELEASE_NAME-mysql-phpmyadmin -o yaml`. |
| Application credentials | `cpln secret reveal CREDENTIALS_SECRET_NAME -o yaml`       | Never stored in the Helm release.                                                                                                                        |
| Root password           | `cpln secret reveal ROOT_SECRET_NAME -o yaml`              | Never stored in the Helm release.                                                                                                                        |

## Important Notes

* **Create both prerequisite secrets before installing.** A reference to a secret that does not exist wedges the workload instead of failing the install.
* **Credentials are read only when the volume is first initialized.** Rotating a secret afterwards does not change the stored passwords — use `ALTER USER` inside MySQL and update the secret to match.
* **Do not scale past one replica.** This is a single instance on a single volume, not a replicated cluster.
* **Data lives on the volume set** and survives redeploys; `cpln helm uninstall` deletes it, taking the database with it.
* **Upgrading from 1.4.3 or earlier is a breaking change** — the removed keys stop the upgrade rather than resetting a password. See [Upgrading From 1.4.3 or Earlier](#upgrading-from-1-4-3-or-earlier).

## External References

<CardGroup cols={2}>
  <Card title="MySQL Documentation" icon="book" href="https://dev.mysql.com/doc/">
    Official MySQL documentation
  </Card>

  <Card title="phpMyAdmin Documentation" icon="database" href="https://docs.phpmyadmin.net/">
    phpMyAdmin user documentation
  </Card>

  <Card title="Cloud Accounts" icon="cloud" href="https://docs.controlplane.com/guides/create-cloud-account">
    Create a Control Plane Cloud Account for backup storage access
  </Card>

  <Card title="Backup Image Source" icon="github" href="https://github.com/controlplane-com/backup-images/tree/main/mysql-backup">
    Source code for the MySQL backup container image
  </Card>

  <Card title="MySQL Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/mysql">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
