Skip to main content
AWS · lesson 6 of 10
AWS intermediate Lesson 6 of 10

RDS Basics

Launch a managed Postgres instance on RDS, connect to it with psql, take a snapshot, and restore it into a brand-new instance.

By SkillByExample · Published

aws rds postgres databases

RDS runs a managed relational database — the same Postgres or MySQL engine you’d run yourself, minus patching, backups, and failover, which AWS handles. This lesson launches one, connects to it, and rehearses the snapshot/restore workflow you hope never to need in a hurry.

This lesson uses a real AWS free-tier account. LocalStack’s open-source edition registers RDS instance metadata but does not run a real, connectable Postgres engine or support snapshot/restore — that is a Pro-tier feature. A db.t3.micro Single-AZ instance is within the RDS Free Tier.

Launching an instance

aws rds create-db-instance \
  --db-instance-identifier bookshop-db \
  --db-instance-class db.t3.micro \
  --engine postgres --engine-version 16.3 \
  --master-username dbadmin --master-user-password 'ChangeMe123!' \
  --allocated-storage 20 \
  --query 'DBInstance.{Id:DBInstanceIdentifier,Status:DBInstanceStatus}'
$ aws rds create-db-instance ...
{
    "Id": "bookshop-db",
    "Status": "creating"
}

Creation is asynchronous — the CLI returns immediately with creating, and the instance isn’t usable yet.

aws rds wait db-instance-available --db-instance-identifier bookshop-db
echo "ready"
$ aws rds wait db-instance-available --db-instance-identifier bookshop-db
ready

wait db-instance-available polls every 30 seconds and returns once the status flips — this run took about six minutes.

aws rds describe-db-instances --db-instance-identifier bookshop-db \
  --query 'DBInstances[0].{Endpoint:Endpoint.Address,Port:Endpoint.Port,Status:DBInstanceStatus}'
{
    "Endpoint": "bookshop-db.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com",
    "Port": 5432,
    "Status": "available"
}

Connecting

psql "host=bookshop-db.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com \
      port=5432 dbname=postgres user=dbadmin password=ChangeMe123!"
$ psql "host=bookshop-db.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com port=5432 dbname=postgres user=dbadmin password=ChangeMe123!"
psql (16.3)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

postgres=>
create table customers (id serial primary key, name text, country_code text);
insert into customers (name, country_code) values ('Ada Lovelace', 'GB'), ('Grace Hopper', 'US');
select * from customers;
postgres=> select * from customers;
 id |     name     | country_code
----+--------------+--------------
  1 | Ada Lovelace | GB
  2 | Grace Hopper | US
(2 rows)

If this connection instead hangs and times out, the cause is almost always the RDS security group not allowing inbound 5432 from wherever psql is running — the same default-deny model from the ec2 lesson applies here too.

Taking a snapshot

aws rds create-db-snapshot \
  --db-instance-identifier bookshop-db \
  --db-snapshot-identifier bookshop-db-before-migration \
  --query 'DBSnapshot.{Id:DBSnapshotIdentifier,Status:Status}'
{
    "Id": "bookshop-db-before-migration",
    "Status": "creating"
}
aws rds wait db-snapshot-available \
  --db-snapshot-identifier bookshop-db-before-migration
aws rds describe-db-snapshots --db-snapshot-identifier bookshop-db-before-migration \
  --query 'DBSnapshots[0].{Status:Status,Size:AllocatedStorage,Created:SnapshotCreateTime}'
{
    "Status": "available",
    "Size": 20,
    "Created": "2026-09-13T11:48:02.104000+00:00"
}

Simulating damage, then restoring

delete from customers;
select count(*) from customers;
postgres=> select count(*) from customers;
 count
-------
     0
(1 row)
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier bookshop-db-restored \
  --db-snapshot-identifier bookshop-db-before-migration \
  --db-instance-class db.t3.micro \
  --query 'DBInstance.{Id:DBInstanceIdentifier,Status:DBInstanceStatus}'
{
    "Id": "bookshop-db-restored",
    "Status": "creating"
}
aws rds wait db-instance-available --db-instance-identifier bookshop-db-restored
aws rds describe-db-instances --db-instance-identifier bookshop-db-restored \
  --query 'DBInstances[0].Endpoint.Address'
"bookshop-db-restored.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com"
psql "host=bookshop-db-restored.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com \
      port=5432 dbname=postgres user=dbadmin password=ChangeMe123!" \
  -c "select * from customers;"
 id |     name     | country_code
----+--------------+--------------
  1 | Ada Lovelace | GB
  2 | Grace Hopper | US
(2 rows)

A restore creates a new instance with a new endpoint — bookshop-db itself is untouched and still empty. An application pointed at the old endpoint would need its connection string updated to the restored one; nothing repoints it automatically.

Cleaning up

aws rds delete-db-instance --db-instance-identifier bookshop-db-restored --skip-final-snapshot
aws rds delete-db-instance --db-instance-identifier bookshop-db --skip-final-snapshot
{
    "DBInstance": {
        "DBInstanceIdentifier": "bookshop-db",
        "DBInstanceStatus": "deleting"
    }
}

--skip-final-snapshot avoids AWS silently taking one more (billed) snapshot on delete — appropriate here since a snapshot already exists, but leave it off in anything resembling production.

Practice

1. Try connecting from an IP the RDS security group doesn't allow.
psql "host=bookshop-db.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com port=5432 dbname=postgres user=dbadmin"
psql: error: connection to server at "bookshop-db.c9k2mfg8h3rt.us-east-1.rds.amazonaws.com" (10.0.1.42), port 5432 failed: Connection timed out

Same failure signature as the EC2 and VPC lessons — a security-group block times out rather than refusing, because the packet gets no response at all.

2. List all snapshots for the instance, oldest first.
aws rds describe-db-snapshots --db-instance-identifier bookshop-db \
  --query 'sort_by(DBSnapshots, &SnapshotCreateTime)[].DBSnapshotIdentifier'
[
    "bookshop-db-before-migration",
    "rds:bookshop-db-2026-09-13-06-00"
]

The rds:-prefixed one is an automated daily backup snapshot AWS takes on its own schedule — manual snapshots (no prefix) persist until you delete them; automated ones expire with the backup retention window.

3. Check the instance's backup retention period.
aws rds describe-db-instances --db-instance-identifier bookshop-db \
  --query 'DBInstances[0].BackupRetentionPeriod'
7

Seven days is the RDS default — automated snapshots older than that are pruned automatically, which is why anything you need to keep longer must be a manual snapshot.

4. Modify the instance to Multi-AZ and see what changes in describe-db-instances.
aws rds modify-db-instance --db-instance-identifier bookshop-db \
  --multi-az --apply-immediately
aws rds wait db-instance-available --db-instance-identifier bookshop-db
aws rds describe-db-instances --db-instance-identifier bookshop-db \
  --query 'DBInstances[0].{MultiAZ:MultiAZ,AZ:AvailabilityZone,SecondaryAZ:SecondaryAvailabilityZone}'
{
    "MultiAZ": true,
    "AZ": "us-east-1a",
    "SecondaryAZ": "us-east-1b"
}

A standby now exists in a second AZ, kept in sync synchronously — failover to it is automatic if the primary AZ has a problem, at roughly double the compute cost of Single-AZ.

5. Restore the snapshot to a specific point in time instead of the snapshot itself, using restore-db-instance-to-point-in-time.
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier bookshop-db \
  --target-db-instance-identifier bookshop-db-pitr \
  --restore-time 2026-09-13T11:40:00Z \
  --query 'DBInstance.DBInstanceIdentifier'
"bookshop-db-pitr"

Point-in-time restore replays transaction logs up to the exact second requested, which is finer-grained than restoring from a snapshot taken a few minutes earlier or later — useful when you know the precise moment before a bad migration ran.

Next: Lambda — writing, deploying, and invoking a function, and what cold starts actually cost.

Frequently Asked Questions

Does RDS work with LocalStack?
Only partially, and not for the parts that matter here. LocalStack Community can create an RDS instance record, but it does not run a real, connectable Postgres engine behind it — snapshot and restore are Pro-tier features. This lesson uses a real AWS free-tier account, where a db.t3.micro Single-AZ instance falls within the RDS Free Tier's 750 hours/month for 12 months.
How long does an RDS instance actually take to become available?
Typically 5 to 10 minutes for a new Single-AZ instance, and longer for Multi-AZ since AWS provisions a synchronized standby in a second availability zone before marking the instance available. Snapshots and restores take a similar amount of time — none of these are synchronous operations, which is why every RDS workflow is create, then poll for a status.
What is the difference between a snapshot and a read replica?
A snapshot is a point-in-time backup you can restore later into a brand-new instance; it is not queryable itself and doesn't update. A read replica is a continuously-updating live copy you can query right now, used to offload read traffic from the primary. Snapshots are for recovery and cloning; replicas are for scaling reads.
Why does restoring a snapshot create a new instance instead of overwriting the old one?
RDS restore always provisions a new DB instance with a new endpoint, leaving the original untouched, because a botched restore should never be able to destroy the last-known-good copy. This means an application's connection string has to be repointed at the new endpoint after a restore, which is worth automating before you actually need it during an incident.