Day 4 of 5
⏱ ~60 minutes
Deploy to AWS — Day 4

RDS — Managed Postgres in Production

Launch a PostgreSQL RDS instance, connect to it securely with VPC, manage credentials with Secrets Manager, and run migrations.

RDS: Managed Relational Databases

RDS runs PostgreSQL, MySQL, and other databases without you managing the server. AWS handles backups, OS patching, failover, and disk expansion. You get the same Postgres you know — without the ops.

Launch a Postgres RDS Instance

Terminal — Create RDS instance
aws rds create-db-instance \
  --db-instance-identifier myapp-postgres \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version "15.4" \
  --master-username appuser \
  --master-user-password "$(openssl rand -base64 20)" \
  --allocated-storage 20 \
  --storage-type gp3 \
  --no-publicly-accessible \
  --vpc-security-group-ids sg-abc123 \
  --backup-retention-period 7 \
  --deletion-protection
⚠️
Always use no-publicly-accessible. Your database should only be reachable from within your VPC — not from the public internet. Your App Runner service connects via VPC connector.

Store Credentials in Secrets Manager

Terminal — Store RDS credentials
aws secretsmanager create-secret \
  --name prod/rds/myapp \
  --secret-string '{
    "host": "myapp-postgres.xyz.us-east-1.rds.amazonaws.com",
    "port": 5432,
    "dbname": "myapp",
    "username": "appuser",
    "password": "your-generated-password"
  }'

Connect from Node.js

Node.js — RDS connection with Secrets Manager
import pg from 'pg';
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

const sm = new SecretsManagerClient({ region: 'us-east-1' });

async function getDbConfig() {
  const res = await sm.send(new GetSecretValueCommand({
    SecretId: 'prod/rds/myapp'
  }));
  return JSON.parse(res.SecretString);
}

let pool;
export async function getPool() {
  if (!pool) {
    const config = await getDbConfig();
    pool = new pg.Pool({
      host: config.host,
      port: config.port,
      database: config.dbname,
      user: config.username,
      password: config.password,
      ssl: { rejectUnauthorized: false }, // RDS requires SSL
      max: 10,
      idleTimeoutMillis: 30000,
    });
  }
  return pool;
}

// Usage
const pool = await getPool();
const result = await pool.query('SELECT NOW()');
console.log(result.rows[0]);

Run Migrations Safely

Terminal — Migrations with node-postgres
npm install node-pg-migrate

# package.json scripts
"migrate:up": "node-pg-migrate up",
"migrate:down": "node-pg-migrate down",
"migrate:create": "node-pg-migrate create"

# Run migrations against RDS (use the credentials from Secrets Manager)
DATABASE_URL="postgresql://appuser:pass@host:5432/myapp" npm run migrate:up
Day 4 Exercise
Launch and Connect to RDS
  1. Launch a db.t3.micro PostgreSQL 15 RDS instance in private mode
  2. Store the credentials in Secrets Manager under prod/rds/myapp
  3. Create a VPC connector for your App Runner service to reach RDS
  4. Connect from a Node.js script using the Secrets Manager integration
  5. Run a simple migration to create a users table

Day 4 Summary

  • RDS manages backups, OS patching, and failover automatically
  • Always create RDS instances as non-publicly-accessible
  • Store database credentials in Secrets Manager — never in .env files
  • App Runner accesses RDS via a VPC connector, not the public internet
  • RDS requires SSL connections — set ssl: rejectUnauthorized: false in pg
Finished this lesson?