AWS · 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
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.