RDS for PostgreSQL
Runway can provision an Amazon RDS for PostgreSQL instance in the AWS account of every Dedicated tenant that enables your module, and wire it into your Helm chart through the Fairway infrastructure.postgresql contract.
Runway owns the instance and its credentials: runwayctl dedicated provision creates the instance and generates the master password, and runwayctl dedicated deploy gives your workload the connection details.
You don’t create or manage the secret yourself.
This page assumes your module is set up for Dedicated. If it isn’t, start with Getting Started.
Overview
Section titled “Overview”An instance is configured in three places, and each setting comes from the first one that sets it:
- The tenant’s override in the Tenant Model, under
modular_features.modules.<module-name>.values_override.infrastructure.rds.<name>. - Your default in the module manifest, under
spec.infrastructure.rds.<name>.defaultConfig, whichcatalog createcopies into the Module Catalog entry. - The built-in default in the field reference.
<name> is the instance’s logical name, a name of your choice that the module manifest and the Tenant Model share.
Entries are merged by logical name and then field by field, so a tenant only sets what differs.
Whether your module needs PostgreSQL at all is its postgresql presence in the Fairway manifest.
Step 1: Declare the dependency in your Fairway manifest
Section titled “Step 1: Declare the dependency in your Fairway manifest”spec: infrastructure: postgresql: presence: REQUIRED| Presence | Module manifest | Tenant Model, after the merge |
|---|---|---|
REQUIRED |
Needs at least one enabled default. | Needs exactly one enabled instance. |
OPTIONAL |
Any defaults, enabled or not, or none. | At most one enabled instance. |
UNUSED |
Must have no defaults, not even disabled ones. | Must have no enabled instance. |
catalog create checks the module manifest against the presence when you release the module.
provision and deploy check the merged result on every tenant.
Don’t set spec.values.infrastructure.postgresql.secretRef yourself.
Runway sets it, and overwrites anything you put there with a warning.
Other postgresql settings, such as clientName or pool sizes, are yours to set and are kept.
Step 2: Set the defaults in your module manifest
Section titled “Step 2: Set the defaults in your module manifest”apiVersion: runway/v3kind: RunwayManifestmetadata: name: example-service-dedicatedspec: infrastructure: rds: main: defaultConfig: databaseName: example instanceType: small diskSizeGb: 50A defaultConfig must be valid on its own, without any override, including when it is disabled.
databaseName has no built-in default, so every RDS defaultConfig sets it.
Field names can be in camelCase or snake_case.
Step 3: Override per tenant, if needed
Section titled “Step 3: Override per tenant, if needed”A tenant that is happy with your defaults needs no infrastructure block.
To change a setting for one tenant, set only that field:
{ "modular_features": { "modules": { "example-service-dedicated": { "enabled": true, "version": "v1.26.0", "values_override": { "infrastructure": { "rds": { "main": { "instance_type": "large", "max_disk_size_gb": 0 } } } } } } }}For this tenant, the instance is large with storage autoscaling turned off, from the Tenant Model; has the database example and 50 GB of storage, from the module manifest; and runs PostgreSQL 18 in two availability zones, from the built-in defaults.
An explicit 0 or false overrides a default, so a tenant can turn off any setting that is on by default.
The override is partial: it only has to parse, with the right types and no unknown fields.
The merged instance is validated as a complete configuration, where an unset field stands for its built-in default.
An override that is valid on its own can still give an invalid result: max_disk_size_gb: 30 fails with the default disk_size_gb: 50 above.
A tenant can also add an instance that has no default, which must then be complete on its own, including database_name.
Field reference
Section titled “Field reference”InfrastructureConfigRDS in runwayctl’s rds.proto.
Unknown fields are rejected.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Provisions the instance. See Enabling and disabling. |
database_name |
string | none | Required. Name of the database created on the instance. Must be a valid unquoted PostgreSQL identifier: ^[a-z_][a-z0-9_]{0,62}$. |
instance_type |
string | xsmall |
Instance type, as a load category. |
disk_size_gb |
integer | 20 |
Allocated storage in GB (gp3). At least 20. |
max_disk_size_gb |
integer | twice disk_size_gb |
Upper bound for storage autoscaling, in GB. Must be greater than disk_size_gb, or 0 to disable autoscaling. |
engine_version |
string | "18" |
PostgreSQL major version, digits only. RDS applies minor upgrades automatically. |
multi_az |
boolean | true |
Deploys a synchronous standby in a second availability zone. |
prevent_db_deletion |
boolean | true |
Enables deletion protection. See Removing an instance. |
password_version |
integer | 1 |
Version of the master password. At least 1. See Rotating the password. |
Logical names are lowercase letters, digits, and hyphens, starting and ending with a letter or digit, at most 45 characters.
Backup retention isn’t configurable per instance.
Every instance keeps automated backups for the tenant backup retention, the Tenant Model’s top-level backup_retention_days (default 30), capped at RDS’s maximum of 35 days.
The tenant’s AWS Backup plan owns the backups.
Instance types
Section titled “Instance types”instance_type takes a load category, which maps to an RDS Graviton instance class:
instance_type |
RDS instance class |
|---|---|
2xsmall |
db.t4g.medium |
xsmall |
db.m7g.large |
small |
db.m7g.xlarge |
medium |
db.m7g.2xlarge |
large |
db.m7g.4xlarge |
xlarge |
db.m7g.8xlarge |
2xlarge |
db.m7g.16xlarge |
Enabling and disabling
Section titled “Enabling and disabling”Every instance has an enabled field.
Unset means enabled.
- Off by default: set
enabled: falsein thedefaultConfig. The defaults are still checked, so that they are sane when a tenant turns the instance on withenabled: true, without repeating the other settings. AREQUIREDPostgreSQL dependency can’t be off by default. - Off for one tenant: the tenant sets
enabled: false. Runway doesn’t provision the instance, deploys noSecretand no Helm values for it, and doesn’t check its other settings. For aREQUIREDdependency, this is an error.
Removing an instance
Section titled “Removing an instance”Disabling an instance that was provisioned before has the same effect as removing it from the Tenant Model: the next provision destroys it.
Deletion protection (prevent_db_deletion, on by default) makes RDS refuse, so removing an instance takes two steps:
- Set
prevent_db_deletion: falsefor the instance in the Tenant Model, and provision the module. - Set
enabled: false, or remove the entry if it has no default, and provision the module again.
What gets created
Section titled “What gets created”provision creates, in the tenant’s AWS account and region:
-
The RDS instance
<tenant-id>-<module-name>-<name>-<digest>. The digest is the first 8 hex characters of the SHA-256 of<account>/<tenant-id>/<module-name>/<name>, so two tenants or modules never collide. If the identifier exceeds 63 characters,<module-name>-<name>is shortened, keeping the tenant prefix and the digest. The instance is placed in the tenant’s database subnets and accepts connections only from the tenant’s<tenant-id>-internal-networkingsecurity group. It is encrypted with the tenant’s customer-managed RDS key from the Tenant Model, or with a key per instance if it has none. -
The database
database_name, owned by the master userrunway. -
A generated master password, applied to the instance through a write-only attribute, so that it is never stored in the Terraform state.
-
Two secrets in AWS Secrets Manager, encrypted with
alias/<tenant-id>-tenant-secrets-kms-key:Secret Keys runway/<module-name>/postgresql/<name>host,port,dbname,usernamerunway/<module-name>/postgresql/<name>/passwordpassword
deploy then:
- writes the Kubernetes
Secretrds-<name>in the module’s namespace, with the keyshost,port,dbname,username, andpassword; - points
infrastructure.postgresql.secretRefin your Helm values at it.
Your service reads the connection details through the Fairway contract, for example with LabKit’s v2/postgres package, the same way as on EKS.
Rotating the password
Section titled “Rotating the password”- Increase
password_versionin the Tenant Model, and provision the module. Runway generates a new password and applies it to the instance and to AWS Secrets Manager in the same apply. - Deploy the module, which updates the Kubernetes
Secret. - Restart the module’s workloads, so that they read the new password.
Only ever increase the version. Connections that are already open keep working, but new connections fail between the provision and the restart.