Networking & Databases: VPC and RDS

VPC subnets, why databases live in private subnets, and the value of a managed database with RDS.

VPC — Virtual Private Cloud

A VPC is your own private, isolated network within AWS — a logically separate slice of the cloud where you control IP address ranges, routing, and what can talk to what. Every resource you create (an EC2 instance, an RDS database) is launched inside a VPC, either a default one AWS creates for you or a custom one you design.

Subnets

A VPC is divided into subnets — smaller IP ranges, each tied to a single Availability Zone. Subnets are classified by how they route traffic to the internet:

  • Public subnet — has a route to an Internet Gateway, so resources inside it can be reached directly from (and reach out to) the public internet. This is where you'd put a load balancer or a web server that needs to accept inbound internet traffic.
  • Private subnet — has no direct route to the internet (or only outbound access via a NAT Gateway, never inbound). Resources here cannot be reached directly from outside the VPC at all.
Plaintext
VPC (10.0.0.0/16)
 ├── Public subnet  (10.0.1.0/24) -- Internet Gateway --> internet
 │     └── Load balancer, bastion host
 └── Private subnet (10.0.2.0/24) -- no inbound route from internet
       └── App servers, RDS database

Why databases usually live in a private subnet

A database has no business being reachable directly from the public internet — every legitimate request to it should come from your application layer, not from an arbitrary client. Putting the database in a private subnet makes that the default, structurally enforced state, rather than something you have to remember to configure correctly in a security group every single time. Even if a security group rule were accidentally left too permissive, a private subnet with no route to the internet still means nothing on the public internet can reach the database directly — it's a second, independent layer of protection (defense in depth), not a substitute for correct security groups.

The typical pattern: a public subnet holds the load balancer and/or bastion host; a private subnet holds application servers and the database; the app servers can still reach the database (same VPC, private routing) and can reach out to the internet for things like package updates via a NAT Gateway, but nothing external can initiate a connection inward to either the app servers or the database.

RDS — Relational Database Service

RDS is AWS's managed relational database offering — you get a MySQL, PostgreSQL, MariaDB, SQL Server, or Oracle database without installing, patching, or operating the database engine yourself.

What RDS actually takes off your plate

Task Self-managed on EC2 RDS
OS + database engine patching You do it, on a schedule you own AWS applies it (configurable maintenance window)
Backups You script and schedule them yourself Automated daily backups + point-in-time recovery, built in
High availability / failover You build and test a failover mechanism Multi-AZ deployment — a standby replica AWS keeps in sync and fails over to automatically
Read scaling You configure replication yourself Read replicas provisioned with a few clicks/one API call
Monitoring You wire up your own metrics Built-in CloudWatch metrics (CPU, connections, storage, replication lag)

Launching an RDS instance (CLI example)

Bash
aws rds create-db-instance \
  --db-instance-identifier myapp-prod-db \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version 16.3 \
  --master-username admin \
  --master-user-password "$DB_PASSWORD" \
  --allocated-storage 20 \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --db-subnet-group-name myapp-private-subnets \
  --no-publicly-accessible \
  --multi-az

--no-publicly-accessible and --db-subnet-group-name pointing at a private subnet group are exactly the enforcement mechanism described above — this database has no public endpoint at all. --multi-az provisions a synchronously replicated standby in a different Availability Zone that RDS automatically promotes if the primary fails.

Why choose RDS over self-managing MySQL/Postgres on EC2

The core trade-off is the same managed-vs-self-hosted pattern that runs through all of AWS: RDS costs somewhat more than an equivalent-sized EC2 instance running the same database engine yourself, but it eliminates a large amount of operational burden — patching, backup scripting, failover engineering, replica setup — that's easy to underinvest in until the day you actually need it (a crashed primary, a security patch for a CVE, a corrupted backup you never tested restoring). For most teams, especially without a dedicated database administrator, that trade is worth it; the exceptions tend to be workloads with unusual configuration needs RDS doesn't expose, or extreme cost sensitivity at very large scale.

Common mistakes

  • Making an RDS instance publicly accessible "temporarily" to debug something, and forgetting to turn it back off.
  • Putting a database in a public subnet by default instead of treating a private subnet as the starting assumption.
  • Skipping Multi-AZ on a production database to save cost, without consciously accepting the downtime risk that trade-off carries.
  • Never testing that a backup or point-in-time recovery actually restores successfully — an untested backup is not a real backup.