1. Docs
  2. Administration
  3. Self-Hosting
  4. Operations
  5. Database

Database Best Practices

    Self-hosting is only available with Pulumi Business Critical. If you would like to evaluate the self-hosted Pulumi Cloud, sign up for the 30-day trial or contact us.

    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:

    CloudDefault instance typeNotes
    AWSdb.r5.large (16 GB RAM)Memory-optimized, recommended for production
    AzureGeneral Purpose D2ads_v5 or equivalent2 vCPU / 8 GB RAM
    GCPdb-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 NameDescription
    DATABASE_CA_CERTIFICATEThe PEM-encoded CA certificate that signed the MySQL server’s TLS certificate. Must be the certificate value, not a file path.
    DATABASE_MIN_TLS_VERSIONThe 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