How to configure PostgreSQL on Amazon RDS for production
Introduction
Amazon Relational Database Service (RDS) runs PostgreSQL for you: AWS provisions the server, applies patches, takes backups and, if you ask for it, keeps a standby ready in a second data center. What RDS doesn't decide for you is how the database fits into your network, who can log in, how much you're willing to pay for availability, and what happens when the PostgreSQL version you picked reaches the end of its support. Those decisions are the difference between a database that is merely running and one that is ready for production.
RDS is one of several ways to host PostgreSQL. This guide assumes you've chosen it and walks through setting it up the way you'd want to run it in production:
- the decisions to make first, and why they matter
- creating the database with the AWS CLI (with notes on the console equivalent)
- connecting securely with
psql - creating application roles instead of using the master user
- operating the database: restores, monitoring and upgrades
- deleting it safely
This guide was written in September 2026 for RDS for PostgreSQL 18. We checked every AWS CLI command and flag against AWS CLI 2.37.6 without an AWS account, so the AWS-side behavior described here comes from AWS's documentation, which we link throughout. The TLS connection, the SSH tunnel and the SQL were tested against local PostgreSQL containers.
Skip the setup with Prisma Postgres
If you want Postgres without provisioning instances, networking, backups, and pooling, Prisma Postgres gives you a hosted database in seconds.
Decisions to make first
Several settings can't be changed after the database exists (encryption at rest is one), and others are cheap to get right up front but disruptive to change later. It's worth deciding them before you create anything.
RDS for PostgreSQL or Aurora PostgreSQL
AWS offers two managed PostgreSQL services. RDS for PostgreSQL runs the community PostgreSQL engine on a DB instance with its own storage volumes. Aurora PostgreSQL-Compatible Edition replaces PostgreSQL's storage layer with a cluster volume that keeps copies of your data across three Availability Zones and grows automatically.
AWS's prescriptive guidance generally recommends Aurora for its faster failover, more read replicas and backups without a performance impact, and says RDS "might still make sense for small to medium workloads" because its wider choice of instance classes can be cheaper. As a rule of thumb, start with RDS for PostgreSQL when you want plain PostgreSQL at a predictable cost, and evaluate Aurora when you need fast failover, many read replicas, or storage that grows without planning. The rest of this guide covers RDS for PostgreSQL.
PostgreSQL version
AWS makes a new major version available on RDS within 30 days of the community's first minor release (x.1) and supports each major version under standard support at least until the community's end of life, according to the RDS for PostgreSQL release calendar:
| Major version | Community support ends | RDS standard support ends | RDS Extended Support ends |
|---|---|---|---|
| 18 | November 14, 2030 | February 28, 2031 | February 28, 2034 |
| 17 | November 8, 2029 | February 28, 2030 | February 28, 2033 |
| 16 | November 9, 2028 | February 28, 2029 | February 29, 2032 |
| 15 | November 11, 2027 | February 29, 2028 | February 28, 2031 |
| 14 | November 12, 2026 | February 28, 2027 | February 28, 2030 |
The community dates are from PostgreSQL's versioning policy. PostgreSQL 13 and older are past RDS standard support. For a new database, choose PostgreSQL 18, the newest major version on RDS; at the time of writing, its latest minor version on RDS is 18.6.
After the end of standard support, RDS Extended Support keeps a major version running for up to three more years with fixes for critical and high-severity security issues, for an additional charge. Watch the default: the console doesn't enroll new instances, but the AWS CLI and the API enroll them unless you pass --engine-lifecycle-support open-source-rds-extended-support-disabled. Without Extended Support, RDS upgrades the instance to a supported major version on or shortly after the end-of-support date. Neither outcome is a substitute for planning your own upgrade (see Major version upgrades). You can look up the dates for a major version with the CLI:
Instance class
The DB instance class sets the CPU, memory and network bandwidth of the server. AWS lists these families among its instance class types:
- General purpose (
db.m9g,db.m8g,db.m7g): balanced CPU and memory on AWS Graviton5, Graviton4 and Graviton3 processors. A good default for production.db.m7i(Intel) anddb.m8a(AMD) are x86 alternatives. - Memory optimized (
db.r8g,db.r7g): more memory per vCPU, for databases whose frequently used data and indexes don't fit in the memory of anmclass. - Optimized Reads (
db.m8gd,db.r8gd): Graviton4 classes with local NVMe SSD storage in addition to the EBS volumes. - Burstable (
db.t4g): a low baseline CPU with the ability to burst. RDS runsdb.t4ginstances in Unlimited mode, so sustained bursting is billed as extra. They suit development, staging and small, spiky workloads, not steady production load. For PostgreSQL 18, check their availability first, as explained below.
Graviton classes use Arm processors, which makes no difference to your application: clients talk to PostgreSQL over the network either way. Memory usually matters more than CPU for a relational database, because data that PostgreSQL finds in memory doesn't have to be read from storage. Start with the smallest size of a general-purpose class that fits your data, watch the metrics described in Monitoring, and change the class when the numbers tell you to. Changing it is a single modify-db-instance call, but the database is unavailable while the change is applied, so schedule it.
Not every class is available for every engine version and Region, and PostgreSQL 18 narrows the choice for now. At the time of writing, AWS's table of supported engine versions per class lists PostgreSQL 18 only for db.m9g, db.m8gd and db.r8gd. For db.m8g, db.r8g, db.m7g, db.r7g, db.m7i and db.t4g, it lists PostgreSQL 17 and older. The table may lag behind what RDS offers, but don't assume: if you want PostgreSQL 18 on one of those classes, run the availability check shown in Creating the database first. This guide's example uses db.m9g.large.
Storage
RDS for PostgreSQL stores data on Amazon EBS volumes. For most databases, the storage type decision is simple:
- General Purpose SSD (
gp3) is the default and a sensible starting point. AWS describes General Purpose storage as best suited for development and testing and recommends Provisioned IOPS for production workloads that need fast, consistent I/O, but many production databases run well ongp3until their latency metrics say otherwise. Below 400 GiB, agp3volume delivers a baseline of 3,000 IOPS and 125 MiB/s. From 400 GiB, RDS stripes the data across four volumes, the baseline rises to 12,000 IOPS and 500 MiB/s, and you can pay for more IOPS and throughput independently of the size. - Provisioned IOPS SSD (
io2Block Express) is for I/O-heavy, latency-sensitive workloads that need consistent sub-millisecond latency. You pay for the IOPS you provision whether you use them or not, so choose it when your metrics show thatgp3isn't enough.io1is the previous generation; AWS recommendsio2where it's available. - Magnetic storage is deprecated and can no longer be used for new instances.
Turn on storage autoscaling by setting a maximum storage threshold. RDS then grows the volume when free space stays at or below 10% for five minutes, which protects you from a classic cause of outages: a full disk. Set the threshold to what you're willing to pay for, and keep in mind that storage can grow but never shrink.
Availability: Single-AZ, Multi-AZ DB instance, or Multi-AZ DB cluster
An Availability Zone (AZ) is one or more data centers in an AWS Region. RDS offers three deployment options that differ in how they survive the loss of an instance or an AZ:
| Deployment | Standbys | Standbys serve reads | Typical failover |
|---|---|---|---|
| Single-AZ DB instance | None | Not applicable | No failover: recovery means a restore or replacement |
| Multi-AZ DB instance | One, in another AZ, synchronously replicated | No | Typically 60–120 seconds |
| Multi-AZ DB cluster | Two, in two other AZs, semisynchronous | Yes | Typically under 35 seconds |
A Multi-AZ DB instance is the usual choice for production: you pay for a second instance that you can't query, and in return a failed instance or AZ costs you a minute or two instead of a restore. It also shortens maintenance, because RDS patches the operating system on the standby first and then fails over. A failover keeps the endpoint's name but points its DNS record at the standby's IP address, so clients have to reconnect and must not cache DNS lookups for long: AWS recommends a DNS TTL of no more than 60 seconds for the Java virtual machine (networkaddress.cache.ttl), because some JVM configurations never refresh cached DNS entries until the JVM restarts. See failing over a Multi-AZ DB instance.
A Multi-AZ DB cluster fails over faster and lets you send read-only queries to the standbys, but it's a different resource type (created with create-db-cluster), it runs only on classes with local NVMe storage such as db.m6gd, db.m8gd and db.r8gd, and it doesn't support the Blue/Green deployments described later for major version upgrades.
A Single-AZ instance is fine for development and staging, and for production workloads that can tolerate the time it takes to restore from a backup.
Network: keep the database private
Every RDS instance lives in a virtual private cloud (VPC), a private network in your AWS account. Three settings control who can reach it:
- A DB subnet group lists the subnets RDS may place the instance in. Use private subnets (subnets without a route to an internet gateway) in at least two AZs, even for a Single-AZ instance, so that you can switch to Multi-AZ later.
- A VPC security group acts as a firewall. Allow PostgreSQL's port only from the security groups of the resources that need it, such as your application servers, instead of from IP ranges.
- Public access decides whether the instance gets a public IP address. Turn it off. With the CLI you have to do this explicitly, because the default depends on which subnet group you use.
Without public access, only resources inside the VPC (or networks connected to it) can reach the database. To reach it from your own computer, you connect through something inside the VPC: port forwarding with AWS Systems Manager Session Manager, a bastion host you connect to with SSH, or a VPN or AWS Direct Connect link into the VPC. An internet gateway doesn't provide this: it only routes traffic for resources that have public IP addresses, and a database without public access has none. AWS describes the common setups in scenarios for accessing a DB instance in a VPC. If you don't have a VPC with private subnets yet, AWS has a tutorial for creating one.
Credentials and authentication
RDS creates one administrative login, the master user. It isn't a PostgreSQL superuser: it's a member of the rds_superuser role, which can create databases, roles and extensions, while AWS keeps the real superuser to itself. Use the master user for administration only, and give applications their own roles (see First steps).
For the master password, let RDS manage it in AWS Secrets Manager. RDS generates the password, stores it in a secret that you control access to with IAM, and rotates it every seven days by default, so the password never has to appear in a script, a chat message or your shell history. The secret is billed at Secrets Manager's rates and is deleted with the instance. Of the features that AWS lists as not working with a managed master password, two apply to RDS for PostgreSQL: creating read replicas and Blue/Green deployments. You can switch the integration off and on later with modify-db-instance.
RDS can also accept IAM database authentication: instead of a password, a database role logs in with a token that is signed with AWS credentials and valid for 15 minutes. This is useful for people and tools that already have IAM identities, and is covered later.
Encryption
Turn on encryption at rest when you create the instance, because you can't add it later without restoring from an encrypted copy of a snapshot. It covers the storage, logs, automated backups and snapshots. The CLI doesn't encrypt by default, so pass --storage-encrypted. RDS then uses the AWS managed key for RDS, or a customer managed AWS KMS key if you pass --kms-key-id. You can't change the key afterwards.
In transit, RDS for PostgreSQL 15 and later require TLS by default: the rds.force_ssl parameter defaults to 1, and connections without TLS are rejected. (On PostgreSQL 14 it defaults to 0.) PostgreSQL 16 and later also support TLS 1.3. RDS signs each instance's server certificate with a certificate authority (CA) that you can choose; the default is rds-ca-rsa2048-g1, and RDS rotates the server certificate before it expires. Requiring TLS only stops eavesdropping, though. To also make sure that you're talking to your database and not an impostor, clients must verify the certificate, which you'll set up in Connecting securely.
Parameter groups
PostgreSQL's configuration (the settings you'd put in postgresql.conf on your own server) lives in a DB parameter group. The default group can't be modified, and switching an existing instance to a new group requires a reboot, so create a custom group for each major version from the start, even if you don't change anything yet.
Backups
With automated backups turned on, RDS takes a daily snapshot of the instance's storage during a backup window and also keeps its transaction logs, so you can restore the instance to any point in time within the retention period, not only to the moment a snapshot was taken. Backups are stored in Amazon S3 (managed by RDS, not in a bucket you can browse), and backup storage is billed as described on the RDS pricing page.
The retention period can be 0 to 35 days (0 turns automated backups off), and the CLI's default is 1 day, which is too short to notice most mistakes. A 14-day retention gives you two weeks to discover a bad deployment or an accidental DELETE; longer periods cost more storage. Copy the instance's tags to its snapshots so that they are easier to find and attribute costs to, and consider replicating backups to another Region if you need to recover from the loss of a whole Region.
Cost
An RDS bill has more parts than the instance price. Expect charges for:
- instance hours, doubled for a Multi-AZ DB instance (the standby is a full instance)
- allocated storage, plus provisioned IOPS and throughput if you add them
- backup storage and snapshots
- RDS Extended Support, if an instance runs past standard support
- the Secrets Manager secret, CloudWatch Logs for exported logs, and Database Insights retention beyond the free tier
- data transfer, depending on where the traffic goes
Check the RDS for PostgreSQL pricing page for your Region, estimate your setup with the AWS Pricing Calculator, and consider reserved instances once the size is stable.
Creating the database with the AWS CLI
You need AWS CLI version 2, credentials for an IAM identity that may create RDS, EC2 security group and Secrets Manager resources, and a VPC with private subnets in at least two AZs. The commands work in Bash and zsh. Start by setting the values that are specific to your account: the Region, the VPC, its private subnets, and the security groups of your application servers (APP_SG) and of your bastion or Session Manager host (BASTION_SG):
Create the security group and subnet group
Create a security group for the database and allow PostgreSQL's port only from the application and the bastion:
Then tell RDS which private subnets it may use:
Create a parameter group
Create a custom parameter group for PostgreSQL 18. Its family, postgres18, has to match the major version:
rds.force_ssl is already 1 by default on PostgreSQL 15 and later; setting it explicitly documents the intent. log_min_duration_statement logs every statement that takes longer than 1,000 milliseconds, which is the cheapest way to find slow queries. Be aware that logged statements can contain data from your tables. The pending-reboot apply method works for both static and dynamic parameters, and since no instance uses the group yet, the values simply take effect when the instance is created. If you want to go further, AWS describes how to refuse MD5 password hashes entirely with rds.accepted_password_auth_method; new passwords on PostgreSQL 14 and later are stored as SCRAM hashes by default anyway.
To see which parameter group family and minor versions RDS offers for a major version, run:
Check that the instance class is available
Instance classes vary by Region and engine version. This command prints the storage types you can use with db.m9g.large and PostgreSQL 18.6, and whether Multi-AZ is available. If it prints nothing, the class isn't offered for that version in your Region, so pick another one:
Create the DB instance
Now create the instance. Every flag here is either a production decision from the previous section or a default you'd otherwise have to remember to change:
What the flags do:
--engine-version 18.6pins the minor version you tested with.--auto-minor-version-upgradethen lets RDS move the instance to newer minor versions during the maintenance window.--multi-azcreates a Multi-AZ DB instance with a standby. Leave it out for a Single-AZ instance.--storage-type gp3 --allocated-storage 100 --max-allocated-storage 500starts with 100 GiB and lets storage autoscaling grow it up to 500 GiB. Below 400 GiB, you don't pass--iopsforgp3, because the baseline performance can't be changed at that size.--storage-encryptedturns on encryption at rest with the AWS managed key.--db-subnet-group-name,--vpc-security-group-idsand--no-publicly-accessibleplace the instance in your private subnets behind its own security group.--ca-certificate-identifier rds-ca-rsa2048-g1sets the CA that signs the server certificate. It's the current default, and the RDS certificate bundle you'll download contains it.--master-username dbadmin --manage-master-user-passwordcreates the master user with a password that RDS generates and stores in Secrets Manager.--db-name appdbcreates a database for your application, in addition to thepostgresdatabase that always exists.--backup-retention-period 14keeps 14 days of point-in-time recovery. The backup and maintenance windows are in UTC, must not overlap, and should fall in your quietest hours.--database-insights-mode standard --enable-performance-insights --performance-insights-retention-period 7turns on CloudWatch Database Insights in Standard mode with the 7 days of query-level history that are included at no charge. The flags still carry the name of Performance Insights, the feature Database Insights replaced.--enable-cloudwatch-logs-exports postgresql upgradesends the PostgreSQL log (including the slow statements configured above) and the upgrade log to CloudWatch Logs, where you pay for ingestion and storage.--deletion-protectionmakesdelete-db-instancefail until you turn it off.
The command doesn't pass --engine-lifecycle-support, so the instance is enrolled in RDS Extended Support, as described in PostgreSQL version. Creating the instance takes a while. Wait for it (the waiter gives up after 30 minutes; run it again if that happens), then read the instance's endpoint and the ARN of the master user's secret:
The console equivalent
You can make the same choices in the RDS console with Create database. We couldn't test the console for this guide, so the labels below are the ones AWS's documentation uses, and the screens may differ. Look for Manage master credentials in AWS Secrets Manager in the credential settings, the Maximum storage threshold under Storage autoscaling, Public access under Additional connectivity configuration, the Certificate authority setting, Enable encryption, and the Database Insights section. Two defaults differ from the CLI: the console turns on deletion protection by default, and it doesn't enroll the instance in Extended Support unless you select Enable RDS Extended Support. AWS's guide to creating a DB instance describes each setting.
Connecting securely with psql
Your application servers inside the VPC can connect to $DB_HOST directly. From your own computer, you first need a way into the VPC, and in both cases the client should verify the server's certificate. The steps below use psql. Drivers built on libpq accept the same connection parameters, and most other drivers have equivalent TLS settings.
Download the RDS certificate bundle
Download AWS's certificate bundle, which contains the root CA certificates for all commercial Regions:
It's a PEM file with many certificates in it. psql accepts the whole file as its list of trusted root CAs. AWS also publishes a bundle for each Region on the certificate bundle page. Trust only the root certificates, not intermediate ones, so that automatic rotation of the server certificate doesn't break your connections.
Open a tunnel into the VPC
With Session Manager port forwarding, an EC2 instance in the VPC relays the connection, and neither the instance nor the database needs an inbound rule for your IP address. The instance needs SSM Agent 3.1.1374.0 or later and permission to be managed by Systems Manager, and your computer needs the Session Manager plugin for the AWS CLI:
If you use a bastion host with SSH instead, forward a local port through it:
Either way, port 15432 on your computer now leads to port 5432 on the database. The examples use 15432 to stay out of the way of a PostgreSQL server you might run locally, and the SSH command binds it to 127.0.0.1 so that other machines on your network can't use your tunnel. Leave the command running and continue in a second terminal.
Connect with certificate verification
Read the master password from Secrets Manager (this uses jq to pick the password field out of the secret's JSON) and pass it to psql in the same command, so it isn't left behind in your shell's environment:
Two parameters do the security work. sslmode=verify-full makes psql check that the server's certificate was signed by a CA in sslrootcert and that it was issued for the host name in host. Weaker modes such as require encrypt the connection but normally don't check the certificate, so they would also accept one presented by an attacker in the middle. And the host name is the reason for hostaddr: through a tunnel, the network connection goes to 127.0.0.1, but the certificate is issued for the RDS endpoint. hostaddr tells psql where to connect, while host stays the name that the certificate must match.
We tested this pattern with PostgreSQL 18.6 behind an SSH bastion in local containers. Because a local server can't have a certificate from AWS, we signed its certificate with our own test CA and appended that CA to the downloaded RDS bundle; against RDS, the bundle alone is what you need. In that test, the stand-in for the RDS endpoint was db.dg-rw-rds.test. Connecting to the tunnel with host=127.0.0.1 and no hostaddr fails verification, as it should:
With hostaddr, the connection succeeds, and psql's banner confirms the encryption:
RDS rotates the master password every seven days, so read it from the secret each time instead of copying it somewhere.
First steps: roles for your application
The master user can create and drop databases and roles, so an application that logs in as the master user can do the same if it's compromised or has a bug. Create two roles instead: app_migrator, which owns the schema and runs your migrations, and app_user, which the application uses at runtime and which can only read and write rows. Connected to appdb as the master user, run:
\password is a psql command that prompts for the new password and sends only its SCRAM hash to the server, so the password doesn't end up in the server log or your shell history. Store the two passwords where your deployment reads its secrets, for example in secrets of your own in Secrets Manager. The default privileges are the part that's easy to forget: without them, app_user couldn't use tables that app_migrator creates in the future. If you already created objects in the schema, grant access to them with GRANT ... ON ALL TABLES IN SCHEMA app.
We ran these statements as a non-superuser role with CREATEDB and CREATEROLE, the attributes RDS gives the master user, on PostgreSQL 15, 16, 17 and 18. After app_migrator had created a table, app_user could insert and select rows but not change the schema:
Point your migrations at app_migrator and your application at app_user, both with sslmode=verify-full. If your application opens many short-lived connections, for example from serverless functions, consider Amazon RDS Proxy: it pools and reuses database connections, protects the database from connection surges, and reconnects to the standby after a failover while preserving application connections. It presents its own certificate from AWS Certificate Manager, so clients that connect through a proxy don't need the RDS certificate bundle. The Data Guide's articles on role management and managing privileges explain the underlying concepts.
Optional: IAM database authentication
For people who need to look at the database occasionally, IAM database authentication avoids another set of passwords. Turn it on for the instance:
Then create a role that logs in with IAM and grant it what it needs, here read-only access to the application's tables:
Don't grant rds_iam to app_user or the master user: a role with rds_iam can only log in with IAM, not with its password. An IAM user or role may connect as ops_reader if one of its policies allows the rds-db:connect action on a resource of the form arn:aws:rds-db:REGION:ACCOUNT_ID:dbuser:DBI_RESOURCE_ID/ops_reader, as described in AWS's policy guide. From inside the VPC, a connection then looks like this:
The token is valid for 15 minutes and is only checked when the connection is opened. AWS notes that IAM authentication uses between 300 and 1,000 MiB of extra memory on the instance, which matters on small instance classes.
Operating the database
Backups and point-in-time recovery
A point-in-time restore never overwrites your instance: it creates a new one from the backups, which you then inspect, copy data from, or switch your application to. First, check how far forward you can restore. RDS uploads transaction logs every five minutes, so the latest restorable time is usually a few minutes in the past:
Then restore to the moment before the mistake, in UTC:
Pass the network settings and the parameter group explicitly, because a restored instance otherwise gets the VPC's default security group and the default parameter group. It gets the source's instance class unless you pass --db-instance-class. For RDS for PostgreSQL, the restore can't turn on Secrets Manager management of the master password (AWS supports that only for Oracle). If the restored instance has no MasterUserSecret, run modify-db-instance --manage-master-user-password --apply-immediately on it to get a new managed password. AWS's restore guide lists the remaining options.
Automated backups disappear with the instance by default, so take a manual snapshot before risky changes and before deleting anything. Manual snapshots are kept until you delete them:
Test restores regularly. A backup you've never restored is a hope, not a plan; the Data Guide's article on backup considerations covers the broader strategy.
Monitoring
RDS publishes CloudWatch metrics for every instance. Set CloudWatch alarms on at least these:
FreeStorageSpace: autoscaling helps, but it can't keep up with every bulk load, and it stops at your threshold.CPUUtilizationandFreeableMemory: sustained high CPU or low memory means it's time for a bigger class or better queries. Ondb.t4g, also watchCPUCreditBalance.DatabaseConnections: every PostgreSQL connection uses memory, and running out of connections looks like an outage to your application.ReadLatency,WriteLatencyandDiskQueueDepth: rising values mean that storage has become the bottleneck.MaximumUsedTransactionIDs: PostgreSQL-specific. If autovacuum falls far behind, transaction ID wraparound can eventually force the database to stop accepting writes.
For query-level analysis, RDS uses CloudWatch Database Insights. AWS ended Performance Insights on July 31, 2026, and moved its users to Database Insights, which shows database load by wait event, SQL statement, host and user. Standard mode, the default, keeps 7 days of detailed metrics at no extra cost. Advanced mode adds features such as fleet-wide views, lock analysis for PostgreSQL and per-query statistics, and is billed separately; you can switch modes without downtime. For operating system metrics per process, turn on Enhanced Monitoring, which needs an IAM role that RDS can use to publish them.
Finally, subscribe to RDS event notifications for failovers, maintenance and low storage, so that you hear about them from AWS before you hear about them from your users.
Maintenance and minor version upgrades
RDS applies operating system and engine patches during the weekly maintenance window. Required patches are infrequent and can be deferred, but not indefinitely. With a Multi-AZ DB instance, RDS patches the operating system on the standby first and then fails over, so the interruption is usually a failover rather than a full outage. Engine version upgrades are different: RDS upgrades the primary and the standby at the same time, so even a Multi-AZ instance is unavailable during an engine upgrade.
With automatic minor version upgrades on, RDS moves the instance to the minor version it has designated as the automatic upgrade target for that major version once it has been tested, not to every new minor release. To see what's scheduled, run:
Engine upgrades don't upgrade extensions. After an upgrade, compare installed_version and default_version in the pg_available_extensions view, and update extensions with ALTER EXTENSION ... UPDATE.
Major version upgrades
Major version upgrades change PostgreSQL's on-disk format and can change behavior, so test them before production does. An in-place upgrade (modify-db-instance --engine-version ... --allow-major-version-upgrade) is the simplest route but takes the database offline for the duration of the upgrade. Blue/Green deployments reduce that to a switchover that "typically takes under a minute": RDS creates a copy of your instance (green), keeps it in sync with production (blue), and lets you upgrade and test the green instance before swapping the endpoints.
For a major version upgrade, RDS keeps the green instance in sync with PostgreSQL's logical replication, which comes with requirements and limitations:
- The blue instance needs a custom parameter group with
rds.logical_replicationset to1, and a reboot for the change to take effect. - Every table needs a primary key, because logical replication can't replicate
UPDATEandDELETEotherwise. - Schema changes (DDL) and large objects aren't replicated. Freeze migrations while the deployment exists.
- Blue/Green deployments don't support master passwords managed in Secrets Manager, so you have to switch to a password you manage for the duration (shown below).
- After switchover, point-in-time recovery for the new production instance starts from when the green instance was created. Keep the old instance until your recovery window has passed.
Before you create the deployment, replace the managed master password with one of your own. read -rs reads it without echoing it or recording it in your shell history. Use a long, generated password and store it in a secret of your own until you turn the integration back on, because RDS deletes its managed secret when you turn management off:
The password is still visible in the process list while the aws command runs, so run it interactively rather than from a script that records its arguments, and rotate the password afterwards if that matters in your environment.
When a new major version is available on RDS, create a parameter group for its family as shown earlier, set TARGET_VERSION to the engine version you want (for example, from describe-db-engine-versions) and TARGET_PARAMETER_GROUP to the new group's name. Then create the deployment, test the green instance, and switch over:
If the switchover doesn't finish within the timeout (300 seconds is also the default), RDS rolls it back and leaves both environments unchanged. During the switchover, RDS renames the instances so that the green one takes over the endpoint app-prod-db, and the old one becomes app-prod-db-old1. Your application reconnects to the same host name without configuration changes, but the name now resolves to a different IP address. AWS recommends that your network and client configuration don't cache DNS for longer than five seconds, the TTL of RDS's DNS records, or applications keep sending writes to the old instance; see AWS's switchover best practices. RDS Proxy avoids the DNS dependency: AWS notes that it shortens switchover downtime by redirecting connections without waiting for DNS propagation.
Once the switchover is done, delete the deployment object (RDS keeps both the new and the old instance) and turn the Secrets Manager integration back on for the new production instance, which has taken over the name app-prod-db:
Deleting the database
Deletion protection has to be turned off first, which is the point of it: deleting a production database should take two deliberate steps.
Unless you took manual snapshots, the final snapshot is your only way back: by default, RDS deletes the instance's automated backups along with it (pass --no-delete-automated-backups to keep them for the rest of their retention period). The master user's secret is deleted too, so if you ever restore the final snapshot, set a new master password or turn on the Secrets Manager integration again on the restored instance.
Snapshots, retained backups, the parameter group, the subnet group and the security group stay behind. Snapshots and retained backups cost money until you delete them, so decide how long you need the final snapshot and put a reminder in your calendar.
Conclusion
A production-ready RDS for PostgreSQL instance comes down to a handful of decisions made early: a supported major version with a plan for its end of life, a Multi-AZ deployment, encrypted gp3 storage with autoscaling, a private network with narrow security group rules, a master password managed by Secrets Manager, and backups kept long enough to notice mistakes. After that, connect with sslmode=verify-full, give your application least-privilege roles, and keep an eye on storage, memory and the upgrade calendar.
Once your RDS database is running, you can use it from TypeScript and Node.js with Prisma ORM. Set up Prisma ORM from scratch walks you through the configuration file, the contract that holds your models, and your first migration. Use the app_user role in your application's connection string and app_migrator for migrations, not the master user. Prisma ORM 8 names the schema in every statement instead of relying on the role's search_path, so put your models in a namespace app { ... } block in the contract; without one, they go in public.
Prisma ORM 8 keeps its record of applied migrations in a schema of its own, prisma_contract, and runs CREATE SCHEMA IF NOT EXISTS "prisma_contract" at the start of every migration. PostgreSQL checks that the role may create schemas in the database before it checks whether the schema exists, so with only the privileges above, migrations fail with permission denied for database appdb, even if you create prisma_contract yourself. Let app_migrator create schemas in appdb, and keep the grant for later migrations:
In PostgreSQL, this privilege also lets app_migrator create other schemas in appdb and install trusted extensions such as pgcrypto there. app_user needs no further privileges. We tested this with Prisma ORM 8.0.0-rc.19 (@prisma/orm-postgres 8.0.0-rc.13) on PostgreSQL 15, 16, 17 and 18.
Create a hosted Postgres database
Provision a production-ready Postgres database in seconds — no instances, networking, or backups to manage.