week-7: PostgreSQL RDS integration — terraform, backend, pipeline

This commit is contained in:
ikechukwu Elizabeth
2026-05-21 03:23:33 +01:00
parent ec768b5f62
commit 6ddd7cba1f
5 changed files with 196 additions and 5 deletions
+32 -1
View File
@@ -278,6 +278,10 @@ jobs:
env:
TF_VAR_aws_account_id: ${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}
TF_VAR_aws_region: ${{ secrets.TF_VAR_AWS_REGION }}
TF_VAR_db_name: ${{ secrets.TF_VAR_db_name }}
TF_VAR_db_username: ${{ secrets.TF_VAR_db_username }}
TF_VAR_db_password: ${{ secrets.TF_VAR_db_password }}
TF_VAR_db_host: ${{ secrets.TF_VAR_db_host }}
terraform-apply:
name: Terraform Apply
@@ -309,8 +313,35 @@ jobs:
- name: Terraform Init
run: terraform init
- name: Terraform Apply
- name: Terraform Apply — provision RDS
run: terraform apply -auto-approve -lock-timeout=5m
env:
TF_VAR_aws_account_id: ${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}
TF_VAR_aws_region: ${{ secrets.TF_VAR_AWS_REGION }}
TF_VAR_db_name: ${{ secrets.TF_VAR_db_name }}
TF_VAR_db_username: ${{ secrets.TF_VAR_db_username }}
TF_VAR_db_password: ${{ secrets.TF_VAR_db_password }}
TF_VAR_db_host: ${{ secrets.TF_VAR_db_host }}
- name: Capture RDS hostname
id: rds
run: |
RDS_HOST=$(terraform output -raw rds_hostname)
echo "hostname=$RDS_HOST" >> $GITHUB_OUTPUT
- name: Terraform Apply — wire EC2 with RDS hostname
run: terraform apply -auto-approve -lock-timeout=5m
env:
TF_VAR_aws_account_id: ${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}
TF_VAR_aws_region: ${{ secrets.TF_VAR_AWS_REGION }}
TF_VAR_db_name: ${{ secrets.TF_VAR_db_name }}
TF_VAR_db_username: ${{ secrets.TF_VAR_db_username }}
TF_VAR_db_password: ${{ secrets.TF_VAR_db_password }}
TF_VAR_db_host: ${{ steps.rds.outputs.hostname }}
- name: Job summary
run: |
echo "### Terraform Apply - $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- RDS hostname: \`${{ steps.rds.outputs.hostname }}\`" >> $GITHUB_STEP_SUMMARY
echo "- EC2 re-provisioned with DB connection wired" >> $GITHUB_STEP_SUMMARY
+1 -1
View File
@@ -4,7 +4,7 @@ WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
RUN npm ci --omit=dev
COPY . .
+7 -1
View File
@@ -57,11 +57,17 @@ resource "aws_instance" "app" {
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
# Run backend
# Run backend — DB credentials passed as environment variables
# Values come from Terraform variables which are sourced from GitHub Secrets
docker run -d \
--name task-backend \
--restart unless-stopped \
-p 3000:3000 \
-e DB_HOST=${var.db_host} \
-e DB_PORT=5432 \
-e DB_NAME=${var.db_name} \
-e DB_USER=${var.db_username} \
-e DB_PASSWORD=${var.db_password} \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
# Wait for backend to be ready
+132
View File
@@ -0,0 +1,132 @@
# ─────────────────────────────────────────────
# RDS — PostgreSQL for Scalable Task Platform
# Week 7 | infra/terraform/rds.tf
# ─────────────────────────────────────────────
# ── DB Subnet Group ──────────────────────────
# RDS needs to know which subnets it can live in.
# We use the private subnets so it is never
# reachable from the public internet.
resource "aws_db_subnet_group" "tasks" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = aws_subnet.private[*].id
tags = {
Name = "${var.project_name}-db-subnet-group"
Project = var.project_name
}
}
# ── Security Group: RDS ──────────────────────
# Only the EC2 instance security group may reach
# the database on port 5432. No public access.
resource "aws_security_group" "rds" {
name = "${var.project_name}-rds-sg"
description = "Allow PostgreSQL access from EC2 only"
vpc_id = aws_vpc.main.id
ingress {
description = "PostgreSQL from EC2"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.ec2.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-rds-sg"
Project = var.project_name
}
}
# ── RDS Parameter Group ──────────────────────
# Explicit parameter group gives you a place to
# tune Postgres settings later without replacing
# the instance.
resource "aws_db_parameter_group" "tasks" {
name = "${var.project_name}-pg16"
family = "postgres16"
tags = {
Name = "${var.project_name}-pg16"
Project = var.project_name
}
}
# ── RDS Instance ─────────────────────────────
resource "aws_db_instance" "tasks" {
identifier = "${var.project_name}-db"
# Engine
engine = "postgres"
engine_version = "16"
instance_class = "db.t3.micro" # Free-tier eligible
# Storage
allocated_storage = 20
max_allocated_storage = 100 # Enable storage autoscaling up to 100 GB
storage_type = "gp2"
storage_encrypted = true
# Credentials — sourced from Terraform variables
# which are populated from GitHub Secrets via the pipeline
db_name = var.db_name
username = var.db_username
password = var.db_password
# Network
db_subnet_group_name = aws_db_subnet_group.tasks.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false
multi_az = false # Single-AZ is fine for this stage
# Maintenance
parameter_group_name = aws_db_parameter_group.tasks.name
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
deletion_protection = false # Set to true before production hardening (Week 10)
# Lifecycle — prevent accidental destroy on credential rotation
lifecycle {
ignore_changes = [password]
}
skip_final_snapshot = true # Change to false and set final_snapshot_identifier for production
tags = {
Name = "${var.project_name}-db"
Project = var.project_name
}
}
# ── Outputs ───────────────────────────────────
# The EC2 user-data script and any future modules
# that need the DB endpoint will read these.
output "rds_endpoint" {
description = "RDS instance endpoint (host:port)"
value = aws_db_instance.tasks.endpoint
}
output "rds_hostname" {
description = "RDS hostname only (no port)"
value = aws_db_instance.tasks.address
}
output "rds_port" {
description = "RDS port"
value = aws_db_instance.tasks.port
}
output "rds_db_name" {
description = "Database name"
value = aws_db_instance.tasks.db_name
}
+22
View File
@@ -37,3 +37,25 @@ variable "private_subnet_cidr" {
type = string
default = "10.0.2.0/24"
}
variable "db_name" {
description = "PostgreSQL database name"
type = string
default = "tasksdb"
}
variable "db_username" {
description = "PostgreSQL master username"
type = string
sensitive = true
}
variable "db_password" {
description = "PostgreSQL master password"
type = string
sensitive = true
}
variable "db_host" {
description = "RDS instance hostname — output from aws_db_instance.tasks.address"
type = string
sensitive = true
}