You delete a todo · ECS on Fargate + DynamoDB

13 steps from your browser to the database and back.

InternetAWS Region · us-east-1VPCPublic subnet · to IGWYour browseron the internetIAMglobal · who can do whatAPI GatewayWebSocket API · live lineCloudWatch Logsapp log linesInternet gatewaythe VPC's doorFargate taskruns the app containerDynamoDBTodo.io's tablesThe live line is already open1You delete a todo2Into the VPC3Through the firewall4Who are you?5Get AWS credentials6Call DynamoDB7Is this allowed?8DynamoDB answers9Tell your other tabs10It shows up instantly11Back to you12Leave a note13

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.
      • 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.
    • API Gateway (WebSocket API · live line): The front door for the app's web requests. A WebSocket API keeps a connection open, so the server can push updates to your browser instantly.
    • CloudWatch Logs (app log lines): Collects the lines your app writes about what it's doing (its logs), so you can search them later.
    • DynamoDB (Todo.io's tables): AWS's serverless NoSQL database: you store and fetch items by key, and it scales by itself with no server to manage.
  • Your browser (on the internet)
  • IAM (global · who can do what): Identity and Access Management: decides who (and which app) may do what in AWS. It's global, the same in every Region.

Steps, in order:

  1. The live line is already open. Path: Your browser → API Gateway. When you logged in, your browser opened a WebSocket to API Gateway. It stays open, so AWS can send you messages any time.
  2. You delete a todo. Path: Your browser. Your browser sends DELETE /todos/42 over HTTPS.
  3. 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.
  4. 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.
  5. 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.
  6. Get AWS credentials. Path: Fargate task. The app gets temporary credentials for its IAM role (the task role) from the ECS credentials endpoint inside the task. No keys are stored in the code.
  7. Call DynamoDB. Path: Fargate task → Internet gateway → DynamoDB. The app calls DeleteItem on the DynamoDB API over HTTPS, signed with those credentials. It leaves through the internet gateway because DynamoDB is a public AWS service, not something inside your VPC.
  8. Is this allowed?. Path: DynamoDB → IAM. DynamoDB checks with IAM: may todo-app-task-role do dynamodb:DeleteItem here? The policy on the role allows it on the table's ARN. Checks: iam: todo-app-task-role: dynamodb:DeleteItem on table/Todos → allow.
  9. DynamoDB answers. Path: DynamoDB → Internet gateway → Fargate task. DynamoDB confirms the item is gone.
  10. Tell your other tabs. Path: Fargate task → Internet gateway → API Gateway. The app looks up your connection id in the database and posts a message to it through the WebSocket API. Checks: iam: todo-app-task-role: execute-api:ManageConnections → allow.
  11. It shows up instantly. Path: API Gateway → Your browser. Every Todo.io tab you have open, on any device, shows the change without a refresh.
  12. Back to you. Path: Fargate task → Internet gateway → Your browser. You get 204 No Content, and the todo disappears.
  13. 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 13
Step 1 of 13
  1. When you logged in, your browser opened a WebSocket to API Gateway. It stays open, so AWS can send you messages any time.

    Show me the real thing

    The WebSocket handshake

    GET /prod HTTP/1.1
    Host: ws123.execute-api.us-east-1.amazonaws.com
    Upgrade: websocket
    Connection: Upgrade
    
    → API Gateway assigns connection id Ab3xYzEXAMPLE=

    Exam tipAn API Gateway WebSocket API has $connect, $disconnect and $default routes. Each one can call Lambda or an HTTP backend.

  2. Your browser sends DELETE /todos/42 over HTTPS.

    Show me the real thing

    The HTTP request

    DELETE /todos/42 HTTP/1.1
    Host: todo.io
    Cookie: session=eyJ1c2VySWQiOiJ1LTEyMyJ9.Zm9v
  3. 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.

  4. 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.

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

  6. The app gets temporary credentials for its IAM role (the task role) from the ECS credentials endpoint inside the task. No keys are stored in the code.

    Show me the real thing

    Trust policy on todo-app-task-role: who may use this role

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "ecs-tasks.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }

    Exam tipECS has two roles. The task role is what your code may do. The task execution role is what ECS itself needs to pull the image and ship logs.

  7. The app calls DeleteItem on the DynamoDB API over HTTPS, signed with those credentials. It leaves through the internet gateway because DynamoDB is a public AWS service, not something inside your VPC.

    Show me the real thing

    The DeleteItem request body

    {
      "TableName": "Todos",
      "Key": {
        "userId": {
          "S": "u-123"
        },
        "todoId": {
          "S": "42"
        }
      }
    }

    Exam tipA VPC gateway endpoint (Level 1) keeps this traffic off the internet, and it costs nothing.

  8. DynamoDB checks with IAM: may todo-app-task-role do dynamodb:DeleteItem here? The policy on the role allows it on the table's ARN.

    • 🔑IAM todo-app-task-role: dynamodb:DeleteItem on table/TodosAllowed
    Show me the real thing

    Permissions policy on todo-app-task-role

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "dynamodb:GetItem",
            "dynamodb:Query",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem"
          ],
          "Resource": [
            "arn:aws:dynamodb:us-east-1:111122223333:table/Users",
            "arn:aws:dynamodb:us-east-1:111122223333:table/Todos"
          ]
        }
      ]
    }

    Exam tipIAM starts from deny. A request is only allowed if some policy explicitly allows it and nothing explicitly denies it.

  9. DynamoDB confirms the item is gone.

  10. The app looks up your connection id in the database and posts a message to it through the WebSocket API.

    • 🔑IAM todo-app-task-role: execute-api:ManageConnectionsAllowed
    Show me the real thing

    Posting to a connection

    POST /prod/@connections/Ab3xYzEXAMPLE= HTTP/1.1
    Host: ws123.execute-api.us-east-1.amazonaws.com
    
    {"type":"todo.deleted","todoId":"42"}

    Exam tipYour browser never asked for this. Anything with permission can push to a connection id through the @connections API.

  11. Every Todo.io tab you have open, on any device, shows the change without a refresh.

  12. You get 204 No Content, and the todo disappears.

    Show me the real thing

    The HTTP response

    HTTP/1.1 204 No Content

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

  13. 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 DELETE /todos/42 user=u-123 status=204 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 approved every database call, using a role. There's no database password anywhere.
  • DynamoDB isn't in your VPC. It's a public AWS service called over HTTPS.
  • A WebSocket stays open, so the server side can send you updates without you asking.
  • You, the customer, were never an IAM user. IAM controls the app, not its customers.