Move it to managed building blocks
Continues from the last build: TaskFlow runs on one hand-built EC2 box you SSH into to deploy.
Last rung you got TaskFlow running on a single EC2 instance: you SSH in, git pull, rebuild the frontend, restart the backend with systemd, and pray nothing broke.
What you'll build
TaskFlow served as a CloudFront + S3 static site over HTTPS, talking to a FastAPI backend running on ECS Fargate behind an Application Load Balancer that connects privately to an RDS Postgres instance, with no self-managed VM in the request path. You can redeploy the frontend with a sync + invalidation and redeploy the backend by pushing a new image and forcing a new ECS deployment, and you understand precisely which click-ops steps Terraform will replace next.
See how we teach, before you sign up
You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:
#!/usr/bin/env bash
set -euo pipefail
REGION="eu-west-1"
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
REPO="taskflow-backend"
TAG="${1:-latest}"
REGISTRY="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"
# Create the repo once; ignore the error if it already exists.
aws ecr describe-repositories --repository-names "$REPO" --region "$REGION" >/dev/null 2>&1 \
|| aws ecr create-repository --repository-name "$REPO" --region "$REGION"
# Log Docker in to ECR using a short-lived token.
aws ecr get-login-password --region "$REGION" \
| docker login --username AWS --password-stdin "$REGISTRY"
docker build -t "${REPO}:${TAG}" ../backend
docker tag "${REPO}:${TAG}" "${REGISTRY}/${REPO}:${TAG}"
docker push "${REGISTRY}/${REPO}:${TAG}"
echo "Pushed ${REGISTRY}/${REPO}:${TAG}"Reading this file
set -euo pipefailStops the script immediately if any command fails or an unset variable is used, so you never push a half-built image.aws sts get-caller-identity --query AccountAsks AWS who you are and pulls out just your 12-digit account number, which is part of the ECR registry address.aws ecr get-login-passwordGets a temporary password so Docker can log in to your private ECR registry.docker push "${REGISTRY}/${REPO}:${TAG}"Uploads the built image to ECR; the ECS task later pulls this exact image to run it.
Builds the backend container and pushes it to a private ECR repo that the ECS task pulls from.
That's 1 of 9 explained code blocks in this single project.
The build, milestone by milestone
- 1
Host the frontend on S3 + CloudFront over HTTPS
4 guided stepsServing the SPA from the EC2 box meant one region, self-managed nginx, and a TLS cert you had to renew by hand. S3 + CloudFront gives you durable storage, a global CDN, and AWS-managed HTTPS for cents, which is the canonical way to host a static frontend on AWS.
- 2
Provision RDS for PostgreSQL in a private subnet
4 guided stepsThe Postgres you installed on EC2 had no automated backups, no managed patching, and shared a disk with the app. RDS gives you point-in-time recovery, minor-version patching, and the option of Multi-AZ failover later, while keeping the data plane off the public internet.
- 3
Push the backend image to ECR and run it on ECS Fargate behind an ALB
4 guided stepsOn EC2 you ran the container under systemd, terminated TLS in hand-rolled nginx, and had a single instance with no autoscaling or self-healing. ECS on Fargate runs the container for you: it restarts failed tasks, can scale the desired count, and the ALB gives you a load-balanced HTTPS endpoint and health checks. We use ECS Fargate (not App Runner, which is closed to new customers) because it is the AWS-recommended serverless container runtime that any account can create. The trade-off versus App Runner is more moving parts here (a task definition, a target group, an ALB, and security groups), which is exactly the click-ops surface the next rung collapses into Terraform.
- 4
Wire the frontend to the live API and redeploy
4 guided stepsVITE_API_URL is baked into the static bundle at build time, so pointing the frontend at the new managed API is a rebuild-and-redeploy, not a config flip. Getting CORS right between two different domains (CloudFront and the ALB) is the exact cross-origin reality you avoided when everything sat on one EC2 box. Note the ALB endpoint is public with no WAF or auth in front of it, so the API's ALLOWED_ORIGINS is the only browser-origin gate, treat any write endpoint accordingly.
- 5
Decommission the EC2 box and account for what is still click-ops
4 guided stepsLeaving the EC2 box running costs money and is a security liability (an unpatched, internet-facing instance). And the act of listing the click-ops steps makes the cost of snowflake infrastructure concrete: nobody else can reproduce this environment, there is no audit trail, and a region or account rebuild would be days of clicking. That pain is the entire reason the next rung exists.
What's inside when you start
You'll walk away with
This is portfolio-grade. Build it free.
Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.
Start building