You open your todo list · ECS on Fargate + RDS MySQL

8 steps from your browser to the database and back.

InternetAWS Region · us-east-1VPCPublic subnet · to IGWPrivate subnet · no internetYour browseron the internetCloudWatch Logsapp log linesInternet gatewaythe VPC's doorFargate taskruns the app containerRDS MySQLdatabase: todoioYou open your todo list1Into the VPC2Through the firewall3Who are you?4Ask the database5The database answers6Back to you7Leave a note8

Text version of this diagram.

What sits where:

  • AWS Region · us-east-1: A Region is a part of the world where AWS has data centers. Most things you build live in one Region. us-east-1 is in northern Virginia. contains:
    • VPC: Virtual private cloud: your own private network inside AWS. Nothing gets in or out except the ways you allow. contains:
      • Public subnet · to IGW: A slice of the VPC that can reach the internet directly, through the internet gateway (IGW). contains:
        • Fargate task (runs the app container): Your app packed as a container, run by AWS on servers you never see. A task is one running copy.
      • Private subnet · no internet: A slice of the VPC with no route to the internet at all. Nothing here can be reached from outside. contains:
        • RDS MySQL (database: todoio): A MySQL database that AWS runs for you, including the server, patches and backups. todoio is the database's name.
      • Internet gateway (the VPC's door): The door between a VPC and the internet. Without one, nothing in the VPC can talk to the internet directly.
    • CloudWatch Logs (app log lines): Collects the lines your app writes about what it's doing (its logs), so you can search them later.
  • Your browser (on the internet)

Steps, in order:

  1. You open your todo list. Path: Your browser. Your browser sends GET /todos over HTTPS.
  2. Into the VPC. Path: Your browser → Internet gateway. todo.io points at the task's public IP, so the request comes in through the VPC's internet gateway. A Fargate task gets a new public IP every time it restarts, so the DNS record has to follow it. In Level 2 todo.io points at a load balancer's DNS name instead, which never changes.
  3. Through the firewall. Path: Internet gateway → Fargate task. The security group on the task's own network card allows HTTPS on port 443, so the request reaches the container. Checks: security-group: todo-app-sg: TCP 443 from 0.0.0.0/0 → allow.
  4. Who are you?. Path: Fargate task. The app checks your signed session cookie and finds your user id, u-123. This is the app's own login, not AWS.
  5. Ask the database. Path: Fargate task → RDS MySQL. The app sends SQL to RDS MySQL in the private subnet on port 3306. It logs in with a database username and password, not with IAM. Checks: security-group: todo-db-sg: TCP 3306 from todo-app-sg → allow; mysql: MySQL user todo_app: SELECT on todoio.todos → allow.
  6. The database answers. Path: RDS MySQL → Fargate task. MySQL returns your 3 todos.
  7. Back to you. Path: Fargate task → Internet gateway → Your browser. You get 200 OK with your todos, and the list appears.
  8. Leave a note. Path: Fargate task → Internet gateway → CloudWatch Logs. The app's log line goes to CloudWatch Logs so Todo.io's engineers can see what happened. Like SQS, it's a public AWS service, so it leaves through the internet gateway.
Step 1 of 8
Step 1 of 8
  1. Your browser sends GET /todos over HTTPS.

    Show me the real thing

    The HTTP request

    GET /todos HTTP/1.1
    Host: todo.io
    Cookie: session=eyJ1c2VySWQiOiJ1LTEyMyJ9.Zm9v
  2. todo.io points at the task's public IP, so the request comes in through the VPC's internet gateway. A Fargate task gets a new public IP every time it restarts, so the DNS record has to follow it. In Level 2 todo.io points at a load balancer's DNS name instead, which never changes.

    Exam tipA subnet is "public" only because its route table sends 0.0.0.0/0 to an internet gateway. Nothing else makes it public.

  3. The security group on the task's own network card allows HTTPS on port 443, so the request reaches the container.

    • 🛡Security group todo-app-sg: TCP 443 from 0.0.0.0/0Allowed
    Show me the real thing

    Inbound rules on todo-app-sg

    Type    Protocol  Port  Source
    HTTPS   TCP       443   0.0.0.0/0

    Exam tipThe classic way to do HTTPS is a free AWS Certificate Manager (ACM) certificate on a load balancer or CloudFront (Level 2). Since 2025 ACM can also sell you an exportable certificate to install on the instance or container itself.

  4. The app checks your signed session cookie and finds your user id, u-123. This is the app's own login, not AWS.

  5. The app sends SQL to RDS MySQL in the private subnet on port 3306. It logs in with a database username and password, not with IAM.

    • 🛡Security group todo-db-sg: TCP 3306 from todo-app-sgAllowed
    • 🗄MySQL MySQL user todo_app: SELECT on todoio.todosAllowed
    Show me the real thing

    The SQL (real code sends the values as parameters, never pasted into the string)

    SELECT id, title, done FROM todos WHERE user_id = 'u-123' ORDER BY created_at;

    Exam tipA security group rule can use another security group as its source: "anything wearing todo-app-sg may connect". Exam favourite.

  6. MySQL returns your 3 todos.

  7. You get 200 OK with your todos, and the list appears.

    Show me the real thing

    The HTTP response

    HTTP/1.1 200 OK

    Exam tipSecurity groups are stateful: a reply to an allowed request goes back out automatically. No outbound rule needed for it.

  8. The app's log line goes to CloudWatch Logs so Todo.io's engineers can see what happened. Like SQS, it's a public AWS service, so it leaves through the internet gateway.

    Show me the real thing

    The log line

    2026-10-11T18:42:07Z INFO GET /todos user=u-123 status=200 14ms

    Exam tipFargate ships container output with the awslogs log driver, using the task execution role.

What to notice

  • AWS runs the server and you ship a container. One task in one Availability Zone is still a single point of failure (Level 2 fixes that).
  • IAM never checked your SQL. MySQL has its own users; IAM only manages the database server itself.
  • The database sits in a private subnet with no route to the internet. Only the security groups you allowed can reach it.
  • The database password lives in the app config here. In production it belongs in Secrets Manager (Level 1).
  • You, the customer, were never an IAM user. IAM controls the app, not its customers.