5. Managed databases: RDS and ElastiCache

RDS runs your PostgreSQL; ElastiCache runs your Redis. Both are managed: AWS handles patches, backups, and HA. Your job is to wire connection strings and security groups correctly.

Chapter 27 ran Postgres and Redis as Compose services, which is fine on a laptop and useless in production: a single container with no off-host backup is one disk failure away from total data loss. RDS and ElastiCache are the AWS-managed equivalents. They expose Postgres and Redis on the wire the same way Chapter 27's containers did (the application code doesn't change), and AWS takes the backups, the failovers, the version patches, and the storage scaling off your hands.

The two pieces of glue this section spends time on are the connection strings (the endpoints RDS and ElastiCache give you, plus the credentials) and the security groups (who's allowed to open a TCP connection on 5432 and 6379). The security-group rule is the one that bites most often: too narrow and ECS can't reach the database at all; too wide and the database is open to the internet.

Why managed databases over containerised databases

You could run PostgreSQL and Redis in ECS containers alongside your application. Many teams start this way. But containerized databases create operational burden that managed services eliminate.

Backups require manual implementation. Containerized PostgreSQL means you're responsible for backup scripts, testing restore procedures, storing backups securely, and ensuring backups actually work when disaster strikes. RDS handles this automatically: daily snapshots, transaction logs for point-in-time recovery, and automated testing of backup integrity.

Scaling storage requires downtime. When your containerized database fills its disk, you need to stop the container, resize the volume, and restart. With RDS, you modify the storage size through the console. RDS scales storage online without downtime. Your application never notices.

High availability requires complex orchestration. To survive container failures, you need primary-replica setup, health checks, automatic failover, and data synchronization. RDS Multi-AZ handles this automatically. The primary fails, RDS promotes the replica, and updates the DNS endpoint. Your application connection string doesn't change. Failover happens in under 60 seconds without manual intervention.

Maintenance requires planning. PostgreSQL security patches, minor version updates, and configuration tuning all require careful timing and testing. RDS provides maintenance windows where AWS applies patches automatically during low-traffic periods you specify.

The cost gap is real but small at this scale; the operational gap is large. A dev team running their own Postgres in containers will eventually spend a weekend on a failed backup restore or a 3am storage-full alarm. RDS removes that weekend from your calendar, and at a Free-Tier db.t3.micro the bill is essentially noise. Containerised databases are still the right call for local dev (Chapter 27's Compose stack stays exactly as it was), but production runs on the managed services.

Creating the RDS PostgreSQL instance

Your News API needs PostgreSQL for storing articles, API keys, and rate limit tracking. You'll create an RDS PostgreSQL instance using the Free Tier eligible configuration: db.t3.micro (or db.t4g.micro) with 20GB storage. This provides enough capacity for hundreds of thousands of articles while staying within Free Tier limits during your first year.

Make: Create RDS PostgreSQL instance via AWS CLI:

Terminal
aws rds create-db-instance \
    --db-instance-identifier news-api-db \
    --db-instance-class db.t3.micro \
    --engine postgres \
    --engine-version 15.4 \
    --master-username newsadmin \
    --master-user-password 'YourSecurePassword123!' \
    --allocated-storage 20 \
    --storage-type gp3 \
    --no-publicly-accessible \
    --backup-retention-period 7 \
    --preferred-backup-window "03:00-04:00" \
    --preferred-maintenance-window "sun:04:00-sun:05:00" \
    --region us-east-1

# This takes 5-10 minutes to provision
# Check status:
aws rds describe-db-instances \
    --db-instance-identifier news-api-db \
    --query 'DBInstances[0].DBInstanceStatus' \
    --output text

# Output: "creating" -> "available"

Important settings explained:

--no-publicly-accessible: The database isn't accessible from the internet. Only resources within your AWS VPC can connect. This is critical security: your database should never be exposed to public internet, even with strong passwords.

--backup-retention-period 7: RDS keeps daily backups for 7 days. You can restore to any point within the last week. Free Tier allows up to 7 days. Production systems often use 14-30 days.

--storage-type gp3: General Purpose SSD (gp3) provides good performance at reasonable cost. It's Free Tier eligible and suitable for most applications. You could use gp2 (older generation) or io1 (provisioned IOPS for high-performance needs), but gp3 is the sweet spot.

Check: Get your database endpoint:

Terminal
aws rds describe-db-instances \
    --db-instance-identifier news-api-db \
    --query 'DBInstances[0].Endpoint.Address' \
    --output text

# Output (save this):
news-api-db.c9z8v7x2y3z4.us-east-1.rds.amazonaws.com

This endpoint address is your PostgreSQL connection string. It looks like a regular database hostname because that's exactly what it is. Your application will connect to news-api-db.c9z8v7x2y3z4.us-east-1.rds.amazonaws.com:5432 exactly like connecting to localhost:5432, just with the RDS endpoint instead.

Save Your Database Password

The master password you set during creation (YourSecurePassword123! in the example) is shown only during creation. RDS doesn't store it in plaintext. If you lose it, you'll need to reset the master password through AWS console or CLI. Store it securely in your password manager or in AWS Secrets Manager.

Configuring security groups for database access

RDS created your database with a default security group that blocks all inbound connections. This is correct security posture: deny everything by default, then explicitly allow only necessary traffic. You need to allow inbound connections from your ECS containers on port 5432 (PostgreSQL's default port).

Security groups act as virtual firewalls. Each security group has inbound rules (what traffic can reach your resource) and outbound rules (what traffic your resource can send). For RDS, you'll create an inbound rule allowing PostgreSQL traffic from ECS.

Make: First, get your RDS security group ID:

Terminal
RDS_SG=$(aws rds describe-db-instances \
    --db-instance-identifier news-api-db \
    --query 'DBInstances[0].VpcSecurityGroups[0].VpcSecurityGroupId' \
    --output text)

echo "RDS Security Group: $RDS_SG"

Now allow PostgreSQL traffic from ECS containers. For now, we'll allow traffic from anywhere within your VPC (we'll tighten this in Section 6 when we create the ECS security group):

Terminal
# Get your default VPC CIDR (usually 172.31.0.0/16)
VPC_CIDR=$(aws ec2 describe-vpcs \
    --filters "Name=isDefault,Values=true" \
    --query 'Vpcs[0].CidrBlock' \
    --output text)

# Add inbound rule for PostgreSQL
aws ec2 authorize-security-group-ingress \
    --group-id $RDS_SG \
    --protocol tcp \
    --port 5432 \
    --cidr $VPC_CIDR

# Verify the rule exists
aws ec2 describe-security-groups \
    --group-ids $RDS_SG \
    --query 'SecurityGroups[0].IpPermissions'

What this means: Any resource within your VPC (including ECS containers we'll create in Section 6) can now connect to RDS PostgreSQL on port 5432. Resources outside your VPC still can't access the database because RDS has --no-publicly-accessible set.

Check: Test connection from your local machine (requires AWS VPN or bastion host, so we'll test from ECS in Section 6).

Creating the ElastiCache Redis instance

Chapter 27's Redis section is where the 500-700ms uncached path collapsed to a 1-5ms cache hit on the same hardware. ElastiCache is the same Redis, just managed: AWS handles patching, failover within the cluster, and replacement of bad nodes. The instance type for this chapter is cache.t3.micro, which is Free-Tier eligible.

Make: Create ElastiCache Redis cluster:

Terminal
aws elasticache create-cache-cluster \
    --cache-cluster-id news-api-cache \
    --cache-node-type cache.t3.micro \
    --engine redis \
    --engine-version 7.0 \
    --num-cache-nodes 1 \
    --region us-east-1

# Takes 5-10 minutes
# Check status:
aws elasticache describe-cache-clusters \
    --cache-cluster-id news-api-cache \
    --query 'CacheClusters[0].CacheClusterStatus' \
    --output text

# Output: "creating" -> "available"

Get your Redis endpoint:

Terminal
aws elasticache describe-cache-clusters \
    --cache-cluster-id news-api-cache \
    --show-cache-node-info \
    --query 'CacheClusters[0].CacheNodes[0].Endpoint' \
    --output json

# Output:
{
    "Address": "news-api-cache.abc123.0001.use1.cache.amazonaws.com",
    "Port": 6379
}

Save this endpoint. You'll use it in your ECS task definition environment variables: redis://news-api-cache.abc123.0001.use1.cache.amazonaws.com:6379.

Configure security group for Redis:

Terminal
# Get ElastiCache security group
REDIS_SG=$(aws elasticache describe-cache-clusters \
    --cache-cluster-id news-api-cache \
    --show-cache-node-info \
    --query 'CacheClusters[0].SecurityGroups[0].SecurityGroupId' \
    --output text)

# Allow Redis traffic from VPC
aws ec2 authorize-security-group-ingress \
    --group-id $REDIS_SG \
    --protocol tcp \
    --port 6379 \
    --cidr $VPC_CIDR

Migrating data from local to RDS

Your local PostgreSQL database contains articles, API keys, and rate limit data from Chapter 26-27 testing. You can migrate this data to RDS for continuity, or start fresh in production. For learning purposes, starting fresh is simpler; the ECS application creates its tables automatically the first time it connects to the empty RDS instance.

Option 1: Start fresh (recommended for learning)

Your News API's SQLAlchemy models include Base.metadata.create_all() or Alembic migrations. When ECS containers start and connect to the empty RDS database, they'll automatically create the required tables. You'll generate new API keys through your admin endpoint after deployment.

Option 2: Migrate existing data (for production continuity)

If you want to preserve local data, use pg_dump to export and psql to import:

Terminal
# Export from local database
pg_dump -h localhost -U newsuser -d newsdb > news_backup.sql

# Import to RDS (requires network access - typically via EC2 bastion or VPN)
psql -h news-api-db.c9z8v7x2y3z4.us-east-1.rds.amazonaws.com \
     -U newsadmin -d postgres < news_backup.sql

For this chapter, we'll use Option 1 (fresh start). Your application handles database initialization automatically, and generating new API keys in production is a security best practice anyway.

Environment variables for managed databases

Your News API reads database connection strings from environment variables. Update these for RDS and ElastiCache endpoints:

Environment Variable Local (Chapter 27) AWS Production
DATABASE_URL postgresql://user:pass@localhost:5432/newsdb postgresql://newsadmin:YourSecurePassword123!@news-api-db.c9z8v7x2y3z4.us-east-1.rds.amazonaws.com:5432/postgres
REDIS_URL redis://localhost:6379 redis://news-api-cache.abc123.0001.use1.cache.amazonaws.com:6379

You'll configure these in your ECS task definition (Section 6). The application code remains unchanged: only connection strings change between local and production.

Never hardcode database passwords in your application code or commit them to Git. Store credentials in environment variables (for development) or AWS Secrets Manager (for production).

Checkpoint: managed databases

Three questions on the decisions that go into the RDS and ElastiCache setup before the chapter moves to ECS.

Select question to reveal the answer:
Why use RDS instead of running PostgreSQL in an ECS container?

RDS provides automated daily backups, point-in-time recovery, automated failover across availability zones, online storage scaling, and managed updates/patches. Running PostgreSQL in containers means implementing all of this yourself, testing backup/restore procedures, handling failover orchestration, and managing maintenance windows manually. The operational complexity and risk of data loss make managed databases the better choice for production unless you have specialized database operations expertise.

What does --no-publicly-accessible mean when creating an RDS instance?

It means the RDS database isn't assigned a public IP address and can't be accessed from the internet. Only resources within your AWS VPC (like ECS containers) can connect to it. This is critical security: databases should never be exposed to public internet access, even with strong passwords, because they contain your application's most valuable data. Public accessibility creates attack surface for automated scanners, brute force attempts, and zero-day exploits.

How do security groups control access to RDS and ElastiCache?

Security groups are stateful firewalls attached to each resource. By default RDS and ElastiCache deny all inbound traffic. To let ECS reach them, add an inbound rule on the right port (5432 for Postgres, 6379 for Redis) with the ECS service's security group as the source. Using the ECS SG as the source (instead of a CIDR like the VPC range) is what keeps the rule tight: only running tasks in that service can connect, not "anything that happens to share the VPC."

Next, in section 6, we write the task definition, register it with an ECS Fargate service, and wire the task's environment variables and Parameter Store secrets so the running container can actually reach the RDS and ElastiCache endpoints we just created.