Deploy it to a single cloud VM
Continues from the last build: TaskFlow runs as containers locally via docker compose.
Last rung you got TaskFlow running as containers on your laptop with `docker compose up`: nginx serving the React build, the FastAPI backend on :8000, and Postgres, all wired together on a compose network.
What you'll build
A publicly reachable TaskFlow running on one EC2 instance via docker compose, fronted by a security group, with a billing alarm guarding your account, and a written, honest list of this design's failure modes that motivates managed services and Infrastructure as Code in the rungs ahead.
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:
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html:ro
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- backend
restart: unless-stopped
backend:
build: ./backend
environment:
DATABASE_URL: ${DATABASE_URL}
expose:
- "8000"
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
pgdata:Reading this file
"80:80"Maps the instance's port 80 to nginx inside the container, so the security group's HTTP rule actually reaches the app../frontend/dist:/usr/share/nginx/html:roMounts your built React files into nginx read-only; you must run the Vite build before starting compose.condition: service_healthyTells the backend to wait until Postgres reports healthy before starting, avoiding crash-on-boot from a not-yet-ready database.pgdata:/var/lib/postgresql/dataStores Postgres data in a named Docker volume so your tasks survive a container restart or recreation.restart: unless-stoppedTells Docker to bring the container back automatically after a crash or a server reboot, unless you stopped it on purpose.
The compose file the EC2 instance runs. Same three services as local, but nginx publishes port 80 to the host so the security group can expose it, and Postgres data lives in a named volume so a container restart does not wipe tasks.
That's 1 of 4 explained code blocks in this single project.
The build, milestone by milestone
- 1
Open an AWS account and set a billing alarm before anything else
4 guided stepsCloud is pay-per-use with no hard stop by default. A billing alarm is your seatbelt: it does not prevent spend, but it tells you the moment something is wrong while the bill is still small. Senior engineers treat 'cost is a first-class signal' as table stakes.
- 2
Launch one EC2 instance with a key pair in eu-west-1
4 guided stepsA VM is the lowest-level compute abstraction you will use on this path. Understanding what you get (an OS, a public IP, a disk) and what you do not get (no app, no TLS, no scaling) is the baseline every higher abstraction is measured against.
- 3
Write a least-privilege security group: SSH from your IP, HTTP from the world
4 guided stepsSecurity groups are AWS's stateful instance firewall and your first real least-privilege decision. Opening SSH to 0.0.0.0/0 is how bots find and brute-force your box within minutes. Scoping 22 to your IP while leaving 80 open to the world is the correct, deliberate split for a public web app.
- 4
Install Docker and Docker Compose on the instance
4 guided stepsTaskFlow ships as containers, so the host needs a container runtime. Doing this manually shows you exactly why a hand-built server is unreproducible: these are imperative steps in your shell history, not in any file, so the next box you build will be subtly different.
- 5
Deploy TaskFlow and reach it on the public IP
4 guided stepsThis proves the full path end to end: security group, host, runtime, app, database, and the readiness probe at /healthz that every later rung reuses. It is also the moment the manual-deploy pain becomes concrete, since shipping a change means re-running these steps by hand.
- 6
Name everything wrong with this and write the post-mortem
4 guided stepsEngineers grow by being able to articulate why a working thing is still not good enough. A single manually-built VM works and is the right starting point, but it is fragile, insecure-by-omission, and unreproducible. Stating that clearly is what separates 'it runs' from 'it is production-ready'.
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