Todo App ECS Deployment Protocol

Author: Benjamin Friedl
Date: 2026-01-20


Overview

This protocol documents the deployment of a Todo application to AWS using:

  • RDS PostgreSQL for the database
  • ECS Fargate for container orchestration
  • Application Load Balancer for traffic distribution
  • ECR for container image storage

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Internet                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Application Load Balancer (todo-alb)                      │
│                 todo-alb-462279294.us-east-1.elb.amazonaws.com               │
│                         Port 80 (Frontend) │ Port 8080 (Backend)             │
└─────────────────────────────────────────────────────────────────────────────┘
                          │                              │
                          ▼                              ▼
┌────────────────────────────────────┐  ┌────────────────────────────────────┐
│     ECS Service: Frontend          │  │     ECS Service: Backend           │
│     (todo-frontend-service)        │  │     (todo-backend-service)         │
│     Image: todo-frontend:latest    │  │     Image: todo-backend:latest     │
│     Port: 80 (nginx)               │  │     Port: 8080 (Spring Boot)       │
└────────────────────────────────────┘  └────────────────────────────────────┘
                                                         │
                                                         ▼
                                        ┌────────────────────────────────────┐
                                        │     RDS PostgreSQL                 │
                                        │     (todo-postgres)                │
                                        │     Port: 5432                     │
                                        └────────────────────────────────────┘

Step 1: Prerequisites

1.1 AWS CLI Installation

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip"
unzip -o /tmp/awscliv2.zip -d /tmp
/tmp/aws/install --install-dir ~/.local/aws-cli --bin-dir ~/.local/bin --update

1.2 AWS Credentials Configuration

Created ~/.aws/credentials with AWS Academy session credentials:

[default]
aws_access_key_id=ASIAZQ3DSWD24RAUZVNJ
aws_secret_access_key=<secret>
aws_session_token=<token>

Created ~/.aws/config:

[default]
region=us-east-1
output=json

1.3 Verify Identity

aws sts get-caller-identity

Result: Connected as user4607900=Benjamin.Friedl@htlstp.at in account 654654484725


Step 2: EC2 Instance (Pre-existing)

An EC2 instance was already available for SSH access to RDS:

PropertyValue
Instance IDi-0c89e8f9df1b19a90
Namewebserver
Public IP44.223.41.167
Key Pairlabsuser.pem

SSH Access:

ssh -i labsuser.pem ec2-user@44.223.41.167

Step 3: Security Groups

3.1 Create EC2-RDS Security Group

aws ec2 create-security-group \
  --group-name ec2-rds \
  --description "Security group for EC2 and RDS connectivity" \
  --vpc-id vpc-073140ca74888ae74

Result: sg-06210763fac9a6886

3.2 Add Inbound Rules to EC2-RDS

# PostgreSQL (self-referencing for RDS access)
aws ec2 authorize-security-group-ingress --group-id sg-06210763fac9a6886 \
  --protocol tcp --port 5432 --source-group sg-06210763fac9a6886

# SSH access
aws ec2 authorize-security-group-ingress --group-id sg-06210763fac9a6886 \
  --protocol tcp --port 22 --cidr 0.0.0.0/0

# HTTP for ECS tasks
aws ec2 authorize-security-group-ingress --group-id sg-06210763fac9a6886 \
  --protocol tcp --port 8080 --cidr 0.0.0.0/0

3.3 Create ALB Security Group

aws ec2 create-security-group \
  --group-name alb-sg \
  --description "Security group for Todo ALB" \
  --vpc-id vpc-073140ca74888ae74

Result: sg-04a09f89ee4523965

3.4 Add Inbound Rules to ALB

# Frontend port
aws ec2 authorize-security-group-ingress --group-id sg-04a09f89ee4523965 \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

# Backend port
aws ec2 authorize-security-group-ingress --group-id sg-04a09f89ee4523965 \
  --protocol tcp --port 8080 --cidr 0.0.0.0/0

3.5 Allow ALB to access ECS

aws ec2 authorize-security-group-ingress --group-id sg-06210763fac9a6886 \
  --protocol tcp --port 8080 --source-group sg-04a09f89ee4523965

3.6 Update EC2 Instance Security Groups

aws ec2 modify-instance-attribute --instance-id i-0c89e8f9df1b19a90 \
  --groups sg-06210763fac9a6886 sg-0212c3909137ac868

Step 4: RDS PostgreSQL Database

4.1 Create DB Subnet Group

aws rds create-db-subnet-group \
  --db-subnet-group-name todo-rds-subnet-group \
  --db-subnet-group-description "Subnet group for Todo RDS" \
  --subnet-ids subnet-0ed3b1a7f851dee19 subnet-0582afbe56ccfac01

4.2 Create RDS Instance

aws rds create-db-instance \
  --db-instance-identifier todo-postgres \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version 15 \
  --master-username todo_user \
  --master-user-password secret123 \
  --allocated-storage 20 \
  --vpc-security-group-ids sg-06210763fac9a6886 \
  --db-subnet-group-name todo-rds-subnet-group \
  --no-publicly-accessible \
  --db-name postgres

4.3 RDS Result

PropertyValue
Identifiertodo-postgres
EnginePostgreSQL 15.14
Classdb.t3.micro
Endpointtodo-postgres.c7yw08ia8xxu.us-east-1.rds.amazonaws.com
Port5432
Usernametodo_user

Step 5: Backend Deployment

5.1 Create Multi-Stage Dockerfile

Updated todo-backend/Dockerfile for building without local Maven:

# Build stage
FROM maven:3.9-eclipse-temurin-23-alpine AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests -q

# Runtime stage
FROM bellsoft/liberica-openjdk-alpine:23
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
COPY --from=build /app/target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EXPOSE 8080

5.2 Build and Push to ECR

# Login to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 654654484725.dkr.ecr.us-east-1.amazonaws.com

# Build
cd todo-backend
docker build -t todo-backend:latest .

# Tag and Push
docker tag todo-backend:latest 654654484725.dkr.ecr.us-east-1.amazonaws.com/todo-backend:latest
docker push 654654484725.dkr.ecr.us-east-1.amazonaws.com/todo-backend:latest

5.3 Create ECS Cluster

aws ecs create-cluster --cluster-name todo-cluster

5.4 Register Task Definition

Created task-definition.json with environment variables for RDS connection:

{
    "family": "todo-backend-task",
    "networkMode": "awsvpc",
    "taskRoleArn": "arn:aws:iam::654654484725:role/LabRole",
    "executionRoleArn": "arn:aws:iam::654654484725:role/LabRole",
    "containerDefinitions": [{
        "name": "todo-backend",
        "image": "654654484725.dkr.ecr.us-east-1.amazonaws.com/todo-backend:latest",
        "portMappings": [{ "containerPort": 8080 }],
        "environment": [
            {
                "name": "SPRING_DATASOURCE_URL",
                "value": "jdbc:postgresql://todo-postgres.c7yw08ia8xxu.us-east-1.rds.amazonaws.com:5432/postgres"
            },
            { "name": "SPRING_DATASOURCE_USERNAME", "value": "todo_user" },
            { "name": "SPRING_DATASOURCE_PASSWORD", "value": "secret123" }
        ]
    }],
    "requiresCompatibilities": ["FARGATE"],
    "cpu": "256",
    "memory": "512"
}
aws ecs register-task-definition --cli-input-json file://task-definition.json

Step 6: Load Balancer Setup

6.1 Create Backend Target Group

aws elbv2 create-target-group \
  --name todo-backend-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id vpc-073140ca74888ae74 \
  --target-type ip \
  --health-check-path /actuator/health

Result: arn:aws:elasticloadbalancing:us-east-1:654654484725:targetgroup/todo-backend-tg/a783ed022bc277be

6.2 Create Application Load Balancer

aws elbv2 create-load-balancer \
  --name todo-alb \
  --subnets subnet-0ed3b1a7f851dee19 subnet-0582afbe56ccfac01 subnet-0ba943fb29703b278 \
  --security-groups sg-04a09f89ee4523965 \
  --scheme internet-facing \
  --type application

6.3 Create Backend Listener (Port 8080)

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:654654484725:loadbalancer/app/todo-alb/c6d91e6fb0cddd74 \
  --protocol HTTP \
  --port 8080 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:654654484725:targetgroup/todo-backend-tg/a783ed022bc277be

6.4 Create Backend ECS Service

aws ecs create-service \
  --cluster todo-cluster \
  --service-name todo-backend-service \
  --task-definition todo-backend-task:1 \
  --desired-count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0ed3b1a7f851dee19,subnet-0582afbe56ccfac01],securityGroups=[sg-06210763fac9a6886,sg-04a09f89ee4523965],assignPublicIp=ENABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:654654484725:targetgroup/todo-backend-tg/a783ed022bc277be,containerName=todo-backend,containerPort=8080"

Step 7: Frontend Deployment

7.1 Update Environment Configuration

Updated todo-frontend/src/environments/environment.ts:

export const environment = {
    baseUrl:
        "http://todo-alb-462279294.us-east-1.elb.amazonaws.com:8080/api/todos",
};

7.2 Build and Push Frontend

cd todo-frontend
docker build -t todo-frontend:latest .
docker tag todo-frontend:latest 654654484725.dkr.ecr.us-east-1.amazonaws.com/todo-frontend:latest
docker push 654654484725.dkr.ecr.us-east-1.amazonaws.com/todo-frontend:latest

7.3 Register Frontend Task Definition

aws ecs register-task-definition --cli-input-json file://frontend-task-definition.json

7.4 Create Frontend Target Group

aws elbv2 create-target-group \
  --name todo-frontend-tg \
  --protocol HTTP \
  --port 80 \
  --vpc-id vpc-073140ca74888ae74 \
  --target-type ip \
  --health-check-path /

7.5 Create Frontend Listener (Port 80)

aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:654654484725:loadbalancer/app/todo-alb/c6d91e6fb0cddd74 \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:654654484725:targetgroup/todo-frontend-tg/7d596ebf9785598f

7.6 Create Frontend ECS Service

aws ecs create-service \
  --cluster todo-cluster \
  --service-name todo-frontend-service \
  --task-definition todo-frontend-task:1 \
  --desired-count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0ed3b1a7f851dee19,subnet-0582afbe56ccfac01],securityGroups=[sg-04a09f89ee4523965],assignPublicIp=ENABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:654654484725:targetgroup/todo-frontend-tg/7d596ebf9785598f,containerName=todo-frontend,containerPort=80"

Step 8: Verification

8.1 Backend Health Check

curl http://todo-alb-462279294.us-east-1.elb.amazonaws.com:8080/actuator/health

Result: {"status":"UP"}

8.2 Frontend Access

curl -s -o /dev/null -w "%{http_code}" http://todo-alb-462279294.us-east-1.elb.amazonaws.com/

Result: 200


Access URLs

ServiceURL
Frontendhttp://todo-alb-462279294.us-east-1.elb.amazonaws.com
Backend APIhttp://todo-alb-462279294.us-east-1.elb.amazonaws.com:8080
Health Checkhttp://todo-alb-462279294.us-east-1.elb.amazonaws.com:8080/actuator/health

Resource Summary

Resource TypeNameID/ARN
VPCdefaultvpc-073140ca74888ae74
EC2 Instancewebserveri-0c89e8f9df1b19a90
RDS Instancetodo-postgrestodo-postgres.c7yw08ia8xxu.us-east-1.rds.amazonaws.com
ECS Clustertodo-clusterarn:aws:ecs:us-east-1:654654484725:cluster/todo-cluster
Backend Servicetodo-backend-serviceRunning (1/1)
Frontend Servicetodo-frontend-serviceRunning (1/1)
ALBtodo-albtodo-alb-462279294.us-east-1.elb.amazonaws.com
Security Group (EC2/RDS)ec2-rdssg-06210763fac9a6886
Security Group (ALB)alb-sgsg-04a09f89ee4523965

Remaining TODO

As per the original readme.md:

  • Fix security groups (destination can also be a security group for better security)

Navigation