# 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)](https://atulcodes.hashnode.dev/aws-cloudwatch-ecs-fargate-monitoring-alerts)
    
*   App with database connection code
    

* * *

## **1\. Create RDS PostgreSQL Instance**

**Terraform Code:**

```bash
# 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!**

```bash
# 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):**

```jsx
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**

```jsx
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:**

```bash
# 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:**

```jsx
// 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:**

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

**Auto-Scaling Storage:**

```json
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)](https://atulcodes.hashnode.dev/github-actions-ecs-fargate-cicd-pipeline).

* * *

If this helped you, I'd really appreciate your support. You can buy me a coffee here: [**buymeacoffee.com/vatul16**](https://buymeacoffee.com/vatul16) ☕

Thanks for reading!
