Skip to main content

Database Integration with AWS RDS (VPC Peering)

Updated 4 min readMarkdown
Part of seriesZero to Production with AWS ECS Fargate: Terraform, CI/CD, RDS, Monitoring
Database Integration with AWS RDS (VPC Peering)

Introduction

Most production apps need a database. In this part, you'll deploy AWS RDS (PostgreSQL) in a private subnet and connect it to your ECS Fargate containers—securely, with VPC peering and security groups.

What You'll Learn:

  • ✅ Create RDS PostgreSQL instance in private subnet

  • ✅ Configure security groups (ECS → RDS access)

  • ✅ Set up database credentials (AWS Secrets Manager)

  • ✅ Connect app to RDS from ECS Fargate

  • ✅ Database backup and auto-scaling

  • ✅ VPC Peering for multi-VPC access

Prerequisites:

  • Part 5 completed (ECS Fargate with monitoring)

  • App with database connection code


1. Create RDS PostgreSQL Instance

Terraform Code:

# rds.tf
resource "aws_db_subnet_group" "private" {
  name       = "ecs-rds-subnet-group"
  subnet_ids = [aws_subnet.private.id]

  tags = {
    Name = "rds-subnet-group"
  }
}

resource "aws_security_group" "rds" {
  name        = "ecs-rds-sg"
  vpc_id      = aws_vpc.main.id
  description = "Allow ECS to connect to RDS"

  ingress {
    description     = "From ECS"
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.ecs.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "rds-security-group"
  }
}

resource "aws_db_instance" "postgres" {
  identifier = "ecs-postgres-db"

  # Engine
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = "db.t3.micro" # Free tier eligible

  # Database credentials (use Secrets Manager in production)
  username = "admin"
  password = var.db_password # Pass via terraform.tfvars
  port     = 5432

  # Storage
  allocated_storage     = 20
  storage_type          = "gp2"
  storage_encrypted     = true
  delete_protection     = true # Prevent accidental deletion

  # Network
  db_subnet_group_name   = aws_db_subnet_group.private.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  publicly_accessible    = false # Private only

  # Backup
  backup_retention_period = 7
  backup_window          = "03:00-04:00"
  auto_minor_version_upgrade = true

  # Monitoring
  monitoring_interval = 30
  monitoring_role_arn = aws_iam_role.rds_monitoring.arn

  tags = {
    Name        = "ecs-postgres"
    Environment = "production"
  }
}

# Output endpoint
output "rds_endpoint" {
  value = aws_db_instance.postgres.endpoint
  description = "RDS endpoint to connect from ECS"
}

Free Tier: db.t3.micro is free for 12 months (750 hours/month).

Expected Cost:

  • After free tier: ~$15/month (db.t3.micro)

  • Storage: $0.10/GB/month


2. Store Credentials in AWS Secrets Manager

Never hardcode passwords in code!

# secrets.tf
resource "aws_secretsmanager_secret" "db_credentials" {
  name = "ecs-production/db-credentials"

  tags = {
    Name = "db-credentials-secret"
  }
}

resource "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = aws_secretsmanager_secret.db_credentials.id

  secret_string = jsonencode({
    username = "admin"
    password = var.db_password
    host     = aws_db_instance.postgres.endpoint
    port     = 5432
    dbname   = "appdb"
  })
}

# Grant ECS access to secret
resource "aws_iam_role_policy_attachment" "ecs_task_secrets" {
  role       = aws_iam_role.ecs_task_role.name
  policy_arn = "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
}

Access Secret in Your App (Node.js):

const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager({ region: 'ap-south-1' });

async function getDBCredentials() {
  const secret = await secretsManager
    .getSecretValue({ SecretId: 'ecs-production/db-credentials' })
    .promise();
  
  return JSON.parse(secret.SecretString);
}

// Usage
const creds = await getDBCredentials();
const { Pool } = require('pg');
const pool = new Pool({
  user: creds.username,
  host: creds.host,
  database: 'appdb',
  password: creds.password,
  port: creds.port,
});

3. Update Task Definition with Database Environment

resource "aws_ecs_task_definition" "app" {
  # ... (previous config)

  container_definitions = jsonencode([
    {
      name      = "ecs-app"
      image     = "${aws_ecr_repository.app.repository_url}:v1"

      # Environment variables (reference Secret ARN)
      secrets = [
        {
          name      = "DB_HOST"
          valueFrom = aws_secretsmanager_secret.db_credentials.arn
        }
      ]

      # Or pass RDS endpoint directly
      environment = [
        {
          name  = "DB_PORT"
          value = "5432"
        },
        {
          name  = "DB_NAME"
          value = "appdb"
        }
      ]
    }
  ])
}

4. Test Database Connection

From ECS Container:

# Exec into container
aws ecs execute-command \
  --cluster ecs-production-cluster \
  --service ecs-app-service \
  --interactive \
  --command "/bin/sh"

# Inside container:
psql postgresql://admin:password@ecs-postgres-db.xxx.ap-south-1.rds.amazonaws.com:5432/appdb

# Expected:
# postgres=#

Health Check:

// Add to app.js
app.get('/health', async (req, res) => {
  try {
    await pool.query('SELECT 1');
    res.json({ status: 'healthy', database: 'connected' });
  } catch (error) {
    res.status(500).json({ status: 'unhealthy', database: error.message });
  }
});

5. Database Backup & Auto-Scaling

Automated Backups (Already configured):

  • Retention: 7 days

  • Backup window: 03:00-04:00 UTC

  • Point-in-time recovery: Enabled

Manual Snapshot:

aws rds create-db-snapshot \
  --db-instance-identifier ecs-postgres-db \
  --db-snapshot-identifier backup-2025-06-04

Auto-Scaling Storage:

resource "aws_db_instance" "postgres" {
  # ... (previous config)

  allocated_storage     = 20
  storage_type          = "gp2"
  auto_minor_version_upgrade = true
  
  # Enable storage auto-scaling
  max_allocated_storage = 100 # Scale up to 100GB automatically
}

Summary

You now have production database integration:

  • ✅ RDS PostgreSQL in private subnet

  • ✅ Security Group (ECS → RDS only)

  • ✅ AWS Secrets Manager for credentials

  • ✅ App connected to database

  • ✅ Automated backups (7-day retention)

  • ✅ Storage auto-scaling

Next Step: Part 7 will add CI/CD with GitHub Actions (modern alternative to CodeDeploy).

About the author

  • Hi, I'm Atul! I am a software engineer with a strong focus on DevOps, Cloud Infrastructure, and automation. I spend my time building scalable CI/CD pipelines, mastering tools like Docker, Terraform, and AWS, and breaking down complex cloud concepts into accessible, hands-on tutorials. I believe the best way to learn is to build, break, and document the process.

No comments yet. Be the first to comment.

Zero to Production with AWS ECS Fargate: Terraform, CI/CD, RDS, Monitoring

Part 6 of 7

A hands-on AWS ECS Fargate series that takes a containerized app from zero to production using Terraform, VPC networking, ECR, Application Load Balancer, blue-green deployments, CloudWatch monitoring, RDS PostgreSQL, Secrets Manager, and GitHub Actions CI/CD.

Up nextCI/CD Automation with GitHub Actions (Modern Alternative)Introduction Instead of manually running terraform apply and docker push, this guide sets up automated CI/CD with GitHub Actions. Every time you push to main, your app builds, tests, pushes to ECR, an

More from this blog