Database Best Practices
Pulumi Cloud uses MySQL 8.0 for metadata, stack state references, and user/organization data. This page covers best practices for database configuration in production self-hosted deployments.
Use a managed MySQL service
Deploy MySQL 8.0 on a managed service rather than self-managing:
- AWS: Amazon Aurora MySQL (recommended) or RDS MySQL
- Azure: Azure Database for MySQL Flexible Server
- GCP: Cloud SQL for MySQL
- On-prem: MySQL 8.0 with InnoDB, configured for replication
High availability configuration
Multi-AZ and read replicas
Deploy your database cluster across multiple availability zones with at least one read replica:
- Aurora: Use a cluster with a writer instance and one or more reader instances in separate AZs. Aurora handles automatic failover to a reader within approximately 30 seconds.
- RDS: Enable Multi-AZ deployment for automatic standby failover.
- Cloud SQL / Azure MySQL: Enable high-availability configuration (regional availability).
Instance sizing
The self-hosted installers default to the following database instance types:
| Cloud | Default instance type | Notes |
|---|---|---|
| AWS | db.r5.large (16 GB RAM) | Memory-optimized, recommended for production |
| Azure | General Purpose D2ads_v5 or equivalent | 2 vCPU / 8 GB RAM |
| GCP | db-g1-small (1.7 GB RAM) | Minimal; upgrade for production use |
For production workloads, start with a memory-optimized instance with at least 16 GB RAM (db.r5.large, db.r6g.large, General Purpose D4s_v3, or equivalent) and scale based on monitoring. Burstable instances (db.t3.*) are acceptable for development and light workloads but may throttle under sustained load.
Zero-downtime migrations
Pulumi Cloud runs database migrations as a separate task before deploying new application versions. For self-hosted deployments:
- Run migrations before updating service containers.
- The self-hosted installers handle this automatically via a dedicated migration task/job.
- If running migrations manually, ensure they complete before restarting services.
See Upgrades for the full update process.
Encrypting connections with TLS
Pulumi Cloud supports TLS-encrypted connections between the API service and MySQL. This encrypts data in transit between the service and database, which is a requirement for many compliance frameworks.
MySQL server requirements
The MySQL server must be configured with:
- A server certificate and private key signed by a Certificate Authority (CA).
- The CA certificate used to sign the server certificate. This is what the Pulumi API service uses to verify the server’s identity.
For managed database services, TLS is typically enabled by default:
- Amazon Aurora / RDS: TLS is enabled by default. Download the RDS CA certificate bundle for your region.
- Azure Database for MySQL: TLS is enforced by default. Download the DigiCert Global Root CA.
- Google Cloud SQL: TLS is enabled by default. Download the server CA from the Cloud SQL instance’s Connections tab.
For self-managed MySQL, configure the server with the --ssl-ca, --ssl-cert, and --ssl-key options pointing to your certificate files. See the MySQL encrypted connections documentation for details.
Hostname verification
The API service verifies that the hostname in PULUMI_DATABASE_ENDPOINT matches the Common Name (CN) or a Subject Alternative Name (SAN) in the server certificate. If the hostname does not match, the connection will fail with a TLS verification error.
- Ensure the certificate includes the database hostname as a DNS SAN (e.g.,
my-db.example.com) or IP SAN (e.g.,10.0.1.50). - For managed services, the certificate typically already covers the service endpoint hostname.
API service configuration
Set the following environment variables on the API service container. Both are required to enable TLS — if either is missing, the service connects without TLS.
| Variable Name | Description |
|---|---|
DATABASE_CA_CERTIFICATE | The PEM-encoded CA certificate that signed the MySQL server’s TLS certificate. Must be the certificate value, not a file path. |
DATABASE_MIN_TLS_VERSION | The minimum TLS version to accept (e.g., 1.2 or 1.3). |
Example:
# Load the CA certificate into the environment variable
export DATABASE_CA_CERTIFICATE="$(cat /path/to/ca-cert.pem)"
export DATABASE_MIN_TLS_VERSION="1.2"
DATABASE_CA_CERTIFICATE must contain the full PEM-encoded certificate text (including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- markers), not a file path. This is a common source of misconfiguration.Migrations container
The database migrations container also supports TLS. Set DATABASE_CA_CERTIFICATE on the migrations container with the same CA certificate value. See API component — Migrations for the full list of migration environment variables.
Verifying TLS is active
After configuring TLS, verify that connections are encrypted by running the following SQL query against your MySQL server:
SHOW STATUS LIKE 'Ssl_cipher';
A non-empty value (e.g., TLS_AES_256_GCM_SHA384) confirms the connection is using TLS. An empty value means the connection is unencrypted.
You can also check the TLS version:
SHOW STATUS LIKE 'Ssl_version';
Requiring TLS on the MySQL server
To ensure all connections use TLS (not just the Pulumi API), configure MySQL to reject unencrypted connections:
-- Require TLS for a specific user
ALTER USER 'pulumi_service'@'%' REQUIRE SSL;
-- Or require TLS for all connections (MySQL 8.0+)
-- Set in my.cnf: require_secure_transport=ON
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.