DEV Community

Cover image for I run infrastructure for the airline industry. Then I deployed a Flappy Bird clone with the same rigor.

I run infrastructure for the airline industry. Then I deployed a Flappy Bird clone with the same rigor.

At work, downtime is not a ticket. It is a flight that does not take off. I have been leading DevOps teams in the airline industry since 2022, and that context shapes how I think about every deployment I touch.

So one weekend I built a game based on one I liked so much: Flappy Bird, but this game is Flappy Docker where a Docker whale dodges containers instead of pipes, and then I did the thing that probably sounds ridiculous: I deployed it to ECS with Terraform, a real domain, HTTPS and autoscaling.

It is a game nobody asked for. It is also the cheapest place I know to practice the whole path, image to registry to orchestrator to DNS, on something where breaking it costs me nothing.

Play it: https://flappy-docker.carolinaherreramonteza.com

Code: https://github.com/carotechie/game-flappy-docker

How it looks Flappy Docker

The game

I kept the game deliberately boring. Vanilla HTML5 Canvas and JavaScript, no framework, no build step, because the game was never the interesting part.

  • index.html and style.css for layout and styling
  • game.js for the game loop, collision detection, scoring and the difficulty ramp (speed goes up every 5 points)
  • Best score saved in localStorage
  • Controls: click, spacebar, arrow up, Enter, or tap on mobile

The whole thing ships in a four-line Dockerfile. nginx:alpine serving static files, nothing else.

FROM nginx:alpine
COPY . /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

Locally that is three commands and you are playing.

docker build -t flappy-docker .
docker run -d -p 8080:80 --name flappy-docker flappy-docker
open http://localhost:8080
Enter fullscreen mode Exit fullscreen mode

No npm install, no runtime version to match, no "works on my machine". That is the part I actually want people to take away from Docker, and it turns out a game makes them care enough to run the commands.

Taking it to AWS

Once it worked locally I wanted it on a real domain. I also wanted to answer a question I keep getting, from people on my team and from people in the community: what does it really cost to run one small container on ECS?

So I built it twice. Both profiles live in the same Terraform and you switch between them with a single variable.

Architecture comparison: Full vs Low Cost

full, around $30 a month
ECR, ECS, ALB, ACM and autoscaling. One to three tasks with CPU target tracking, HTTPS with a free ACM certificate, and an ALB health checking the tasks and replacing them when they die.

This is what I would ship at work. The load balancer is most of the bill, and it is also most of the value: it gives you something stable to point DNS at, and it recovers on its own.

low_cost, around $9 a month
ECR, ECS and Route 53. One task, fixed. HTTP only. No load balancer.

That last part creates a real problem. Without an ALB there is nothing stable to point DNS at, so a small helper script resolves the running task's public IP at terraform apply time and writes an A record with a low TTL.

I want to be blunt about what that costs, because posts that show you the cheap option tend to skip this part. If the task restarts between applies, that record points at an IP that no longer exists. It stays wrong until the next apply. Nothing is watching and nothing heals itself.

For a game on my personal domain I can live with that. For anything with people waiting on the other end, I would not.

The Terraform

terraform/
├── components/          # reusable modules
│   ├── network/           # default VPC/subnets lookup
│   ├── ecr/                # image repository
│   ├── dns/                 # existing Route53 hosted zone lookup
│   ├── acm/                  # free TLS cert, DNS-validated, full mode only
│   ├── alb/                   # load balancer + listeners + DNS alias, full mode only
│   ├── ecs/                    # cluster, task definition, service
│   ├── autoscaling/             # target-tracking policy, full mode only
│   └── dns_task_record/          # IP-synced DNS record, low_cost mode only
├── vars/
│   ├── prod.tfvars                # deployment_mode = "full"
│   ├── prod-lowcost.tfvars        # deployment_mode = "low_cost"
│   ├── dev.tfvars
│   └── backend-*.hcl              # per-environment S3 backend config
├── main.tf / variables.tf / outputs.tf
Enter fullscreen mode Exit fullscreen mode

A deployment_mode variable decides which modules exist. The ACM, ALB and autoscaling modules carry count = local.full_mode ? 1 : 0, so choosing a profile is just choosing a .tfvars file.

State lives in S3 with Terraform 1.10 native locking (use_lockfile = true), which finally means no DynamoDB table sitting there just to hold a lock.

Deploying it

cd terraform
terraform init -backend-config=vars/backend-prod.hcl
terraform apply -target=module.ecr -var-file=vars/prod.tfvars   # create the repo first

REPO_URL=$(terraform output -raw ecr_repository_url)
docker build -t "$REPO_URL:latest" ..
docker push "$REPO_URL:latest"

terraform apply -var-file=vars/prod.tfvars
Enter fullscreen mode Exit fullscreen mode

The ECR repository has to exist before you can push an image to it, which is why that first apply is targeted. It is the kind of ordering detail that is obvious in hindsight and annoying the first time.

Terraform Apply 1

Terraform Apply 2

After that the service comes up and the tasks register behind the target group.

ECS Tasks

Target Group

And the game runs on its own subdomain over HTTPS, on a certificate that costs nothing.

Route 53 setup

What I would do differently

If I ever needed low_cost mode to stay up without me watching it, I would replace my script with Cloud Map public DNS namespaces. That is the native AWS answer to this problem. It also brings Route 53 delegation complexity that I did not want to take on for a weekend project.

CI/CD is the obvious next step: build, push, and ecs update-service --force-new-deployment on every push to main. Right now I do it by hand, which is fine until it is not.

Why bother with any of this

The honest reason is that a side project is the only place where an expensive mistake is free. I cannot try out a routing decision on a system where a bad afternoon means people stuck at a gate. I can try it here, get it wrong, and fix it on a Sunday.

If you are learning ECS or Terraform, my advice is to pick something you actually want to see running. It beats another tutorial you will abandon halfway.

Try it

Live: https://flappy-docker.carolinaherreramonteza.com

Code: https://github.com/carotechie/game-flappy-docker

Full deployment walkthrough for both modes: docs/SetupOnAWS.md, and in Spanish at docs/SetupOnAWS.es.md.

If you dodge more than 10 containers, you are doing better than me.

Top comments (0)