Skip to content

Paristech1/Local-aws-terraform-infra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

🐳 WrestleDream — Floci + Docker + Terraform Local AWS Runbook

Built by Paris | Floci + Terraform + Docker | May 2026


What We Built

WrestleDream is a daily wrestling performance showdown app. The backend is a FastAPI Python service that fetches real match results, scores wrestlers, and returns matchup pairs. The frontend is React/Vite. The goal of this session was to deploy the entire stack locally using Floci as a local AWS emulator, with Terraform managing every cloud resource as code, all running inside Docker on a Mac Mini M-series.


The Full Stack

Layer What it does
Docker Runs Floci container — emulates all AWS services on port 4566
Floci Local AWS emulator — S3, Lambda, API Gateway, DynamoDB, SSM, IAM, CloudWatch
Terraform Infrastructure as Code — provisions all 20 AWS resources against Floci
Lambda Python 3.11 ARM64 — runs FastAPI via Mangum ASGI adapter
API Gateway HTTP endpoint routing /api/* to Lambda
DynamoDB Two tables — WrestlerCache and WrestleDreamDailyDeck
S3 Hosts the React/Vite frontend build (static site)
SSM Stores env vars (USE_SEED, USE_THESPORTSDB, etc.)
Vite Dev Server Proxies /api/* to Floci API Gateway for local browser interaction

Prerequisites (One-Time Installs)

Install all tools before starting. These only need to be done once.

Tool Install Command Verify
Terraform brew install terraform terraform -v
AWS CLI brew install awscli aws --version
Docker docker.com/get-started docker -v
Python 3.11 brew install python@3.11 python3 --version
Node 18+ brew install node node -v

Terminal Layout

You will use 3 terminal tabs throughout this session. Keep them organized exactly like this:

Terminal Purpose What's Running
Terminal 1 Floci daemon docker run command — stays open the whole session
Terminal 2 Main working terminal Clone, scripts, terraform, npm, git commands
Terminal 3 Verification curl health checks and aws verify commands

⚠️ Every command in this doc specifies which terminal to paste it into. Always follow the tab number.


Phase 0 — Configure Fake AWS Profile

Floci does not need real AWS credentials, but the AWS CLI still expects a profile to exist. Run this once.

In Terminal 2:

aws configure --profile floci
# AWS Access Key ID: test
# AWS Secret Access Key: test
# Default region name: us-east-1
# Default output format: json

This creates a ~/.aws/credentials entry with fake values. Floci accepts any credentials.


Phase 1 — Start Floci

Floci runs as a Docker container. It emulates all AWS services on port 4566. Start it in Terminal 1 and leave it running for the entire session.

In Terminal 1 — leave this running:

docker run --rm -p 4566:4566 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  floci/floci:latest

In Terminal 3 — verify Floci is up:

curl http://localhost:4566/_floci/health
# Expected: {"status":"running"}

💡 The --rm flag auto-removes the container when it stops so it won't pile up.

Port Conflict Fix

If Floci fails to start with a port binding error, an existing container may be holding port 4566.

In Terminal 2:

# See what's running
docker ps -a
# Force remove the conflicting container
docker rm -f <container_id>
# Then retry Phase 1

Phase 2 — Clone the Repo

In Terminal 2:

git clone https://github.com/Paristech1/WrestleDream
cd WrestleDream

If the repo already exists locally you will see fatal: destination path already exists. That's fine — just cd into the existing folder.


Phase 3 — Package the Lambda

The FastAPI backend must be zipped into a Lambda deployment package before Terraform can deploy it. This script handles everything.

In Terminal 2 — from WrestleDream root:

bash terraform/package_lambda.sh

Expected output: ✅ Done! ~8MB → terraform/lambda.zip

What the Script Does

  • Installs requirements.txt + mangum using --platform manylinux_2_17_aarch64 (Linux ARM64 binary wheels)
  • Copies backend/ source files to the build folder
  • Copies api/index.py as the Lambda entry point
  • Appends handler = Mangum(app, lifespan='off') so Lambda knows what to call
  • Zips everything into terraform/lambda.zip
  • Verifies the pydantic_core .so binary is ELF ARM64 — NOT macOS Mach-O

Phase 4 — Terraform Init and Deploy

Terraform reads main.tf and provisions all infrastructure against Floci. Run from inside the terraform/ directory.

In Terminal 2:

cd terraform
terraform init
terraform plan
terraform apply -auto-approve
  • terraform init — downloads the AWS provider plugin (one-time)
  • terraform plan — dry run, shows all ~20 resources to be created
  • terraform apply -auto-approve — deploys everything to Floci in under 30 seconds

Resources Terraform Creates

Resource Name Purpose
S3 Bucket wrestledream-frontend Hosts the React build
DynamoDB Table WrestlerCache Caches wrestler images and scores
DynamoDB Table WrestleDreamDailyDeck Caches daily deck results
SSM Parameters /wrestledream/* USE_SEED, USE_THESPORTSDB, CACHE_TTL_SECONDS, ALLOWED_ORIGINS
IAM Role wrestledream-lambda-role Lambda execution permissions
Lambda Function wrestledream-api Python 3.11 ARM64 FastAPI backend
API Gateway wrestledream-api HTTP /api/* routing to Lambda
API Gateway Stage dev Live URL for all API calls
CloudWatch Log Group /aws/lambda/wrestledream-api Lambda logs, 7-day retention

Phase 5 — Build and Upload the React Frontend

In Terminal 2:

cd ~/WrestleDream/WrestleDream/frontend
npm install
npm run build

Then upload the build to Floci S3:

In Terminal 2:

aws --endpoint-url=http://localhost:4566 \
  s3 sync dist/ s3://wrestledream-frontend \
  --profile floci

Expected: 4 files uploaded — index.html, assets/index.css, assets/index.js, vite.svg


Phase 6 — Test Everything

Verify Resources Exist

In Terminal 3:

aws --endpoint-url=http://localhost:4566 s3 ls --profile floci
aws --endpoint-url=http://localhost:4566 dynamodb list-tables --profile floci
aws --endpoint-url=http://localhost:4566 lambda list-functions --profile floci

Test Lambda Directly

In Terminal 3:

aws --endpoint-url=http://localhost:4566 lambda invoke \
  --function-name wrestledream-api \
  --profile floci \
  --cli-binary-format raw-in-base64-out \
  --payload '{"version":"2.0","routeKey":"GET /api/health","rawPath":"/api/health","headers":{"content-type":"application/json"},"requestContext":{"http":{"method":"GET","path":"/api/health","protocol":"HTTP/1.1","sourceIp":"127.0.0.1","userAgent":"curl"},"stage":"dev"},"isBase64Encoded":false}' \
  /tmp/health.json && cat /tmp/health.json

Expected: {"statusCode":200,"body":"{\"status\":\"ok\",\"app\":\"WrestleDream\"}"}

⚠️ Must use API Gateway v2 payload format (version: "2.0") with sourceIp in requestContext.http. The v1 format causes Mangum to fail.

Test API Gateway Endpoint

In Terminal 3:

API_ID=$(aws --endpoint-url=http://localhost:4566 apigateway get-rest-apis \
  --profile floci --query 'items[0].id' --output text)
curl "http://localhost:4566/restapis/$API_ID/dev/_user_request_/api/health"

Expected: {"status":"ok","app":"WrestleDream"}

Open in Browser

Floci doesn't add CORS headers, so the frontend can't be served directly from S3 in a browser. Instead, run Vite's dev server pointing at Floci — Vite's proxy handles routing and adds CORS headers automatically.

In Terminal 2:

cd ~/WrestleDream/WrestleDream/frontend
WRESTLEDREAM_API="http://localhost:4566/restapis/<api_id>/dev/_user_request_" \
  npm run dev -- --config vite.config.dev.js

Then open http://localhost:5173 (or 5174 if 5173 is in use) in your browser.

Every /api/* call routes: Browser → Vite proxy → Floci API Gateway → Lambda → FastAPI


Phase 7 — Tear Down

This is the core demo of Floci + Terraform: instant destroy and full redeploy in under 30 seconds.

In Terminal 2:

cd ~/WrestleDream/WrestleDream/terraform
terraform destroy -auto-approve

In Terminal 1:

# Press Ctrl+C to stop Floci

To Start Fresh

  1. Start Floci again — Phase 1
  2. Run bash terraform/package_lambda.sh if code changed — Phase 3
  3. Run terraform apply -auto-approve — all resources back in ~30 seconds

Phase 8 — Commit Your Terraform Files

In Terminal 2 — from WrestleDream root:

echo "terraform/.terraform/" >> .gitignore
echo "terraform/lambda.zip" >> .gitignore
echo "terraform/terraform.tfstate*" >> .gitignore
echo "terraform/.lambda_build/" >> .gitignore
git add terraform/main.tf terraform/variables.tf \
  terraform/outputs.tf terraform/package_lambda.sh .gitignore
git commit -m "feat: add Terraform IaC + Floci local dev setup"
git push

Never commit lambda.zip, .terraform/, or tfstate — these are build artifacts and contain local paths.


Issues We Hit and How We Fixed Them

These were real blockers encountered during this session. Each one is documented so you can recognize and fix them fast.

🔴 Issue 1 — Lambda 30-Second Timeout

What happened: Lambda invocation returned FunctionError: Handled with a 30-second wait every time.

Why: Two compounding bugs. First, Terraform's architectures field was missing so Lambda defaulted to x86_64. Floci on Mac M-series runs ARM64 — the binary mismatch caused pydantic_core to fail on import and the Lambda never started. Second, api/index.py had an unconditional sys.path.insert pointing to a backend/ subdirectory that doesn't exist inside the Lambda zip.

Fix: Added architectures = ["arm64"] to aws_lambda_function in main.tf. Fixed api/index.py to use a conditional os.path.isdir() check. Rebuilt the zip with --platform manylinux_2_17_aarch64.


🔴 Issue 2 — Wrong pip Platform Flag

What happened: pydantic_core._pydantic_core ImportModuleError — Lambda couldn't load the C extension.

Why: pip downloaded macOS ARM64 (darwin_arm64) wheels from its local cache because the old flag --platform manylinux2014_aarch64 is an alias that pip can mis-resolve. The .so file was a Mach-O binary instead of Linux ELF.

Fix: Run pip cache purge first, then use --platform manylinux_2_17_aarch64 (explicit new-style tag). The package_lambda.sh script verifies the .so with the file command — it must say ELF ARM aarch64, not Mach-O.


🔴 Issue 3 — AWS CLI Payload Error

What happened: aws lambda invoke returned an error about base64 encoding on the --payload flag.

Why: Newer AWS CLI v2 requires --cli-binary-format raw-in-base64-out when passing a JSON string to --payload.

Fix: Always include --cli-binary-format raw-in-base64-out in every lambda invoke command.


🔴 Issue 4 — Mangum 'sourceIp' KeyError

What happened: Lambda responded with errorType: KeyError: 'sourceIp' when invoked through the CLI.

Why: Mangum v0.17+ requires the API Gateway v2 payload format (version: "2.0" with requestContext.http.sourceIp). The v1 format (httpMethod/path) causes Mangum to fail parsing the event.

Fix: Switch to the v2 payload format shown in Phase 6 test commands. Always include sourceIp and userAgent in requestContext.http.


🔴 Issue 5 — Terraform Partial Destroy Blocking Architecture Update

What happened: terraform destroy errored on API Gateway 404 and only destroyed 9 of 20 resources. Subsequent terraform apply updated Lambda but never applied the arm64 architecture change.

Why: Floci wipes its in-memory resource state between restarts. When Terraform tried to destroy the API Gateway, Floci returned 404 because the resource was already gone. The partial destroy left Lambda in place with x86_64 architecture.

Fix: Bypass Terraform for the architecture update using AWS CLI directly:

aws --endpoint-url=http://localhost:4566 lambda update-function-code \
  --function-name wrestledream-api --profile floci \
  --zip-file fileb:///Users/me/WrestleDream/WrestleDream/terraform/lambda.zip \
  --architectures arm64

🔴 Issue 6 — API Gateway Stage Not Found

What happened: curl to the API Gateway URL returned {"message":"Stage not found"} even after terraform apply succeeded.

Why: The aws_api_gateway_deployment resource used the deprecated stage_name attribute. When Floci's state was wiped between sessions, the stage disappeared.

Fix: Replaced the deprecated stage_name on the deployment resource with a separate aws_api_gateway_stage resource:

resource "aws_api_gateway_stage" "dev" {
  rest_api_id   = aws_api_gateway_rest_api.main.id
  deployment_id = aws_api_gateway_deployment.main.id
  stage_name    = "dev"
}

Added a triggers block to force redeployment when the integration changes. Added lifecycle { create_before_destroy = true }.


🔴 Issue 7 — S3 Frontend White Page

What happened: Opening the S3 URL in a browser showed a white page. The React app loaded but API calls failed.

Why: The frontend's DAILY_DECK_URL was set to the relative path /api/daily-deck. When served directly from S3, there was no proxy to route that to the API Gateway URL. Floci also does not add CORS headers to API Gateway responses.

Fix: Run the frontend on Vite's dev server with WRESTLEDREAM_API pointing at Floci. The devApiPlugin.js proxy handles routing and adds Access-Control-Allow-Origin: *. Full stack: Browser → Vite proxy → Floci API Gateway → Lambda → FastAPI.


🔴 Issue 8 — Port 4566 Already in Use

What happened: Docker failed to start Floci because port 4566 was already bound.

Why: A previous Floci container was still running or stopped but not removed.

Fix:

docker ps -a          # find the conflicting container
docker rm -f <id>     # force remove it
# then retry Phase 1

Key Concepts

Why ARM64 Matters on Mac M-Series

Mac M1/M2/M3/M4 chips are ARM64. When pip installs packages natively on your Mac, it downloads darwin_arm64 binaries. Lambda runs on Linux. If you package those native binaries into the Lambda zip, they crash immediately because Linux cannot execute macOS Mach-O binaries. You must cross-compile using pip's --platform flag to get the correct Linux ELF binaries. Floci on Mac M-series runs Lambda in ARM64 mode, so the correct target is manylinux_2_17_aarch64.

Why Floci Loses State on Restart

Floci stores all resource state in memory, not on disk. Every time the Docker container stops, everything is wiped. This is why Terraform may show resources as existing in its .tfstate but Floci returns 404 when trying to delete them. Always run terraform apply after restarting Floci to reconcile state. This behavior is also what makes Floci perfect for rapid teardown/redeploy demos.

Mangum — The ASGI Bridge

Lambda expects a specific event format and a synchronous handler function. FastAPI uses ASGI, which is asynchronous and expects a different calling convention. Mangum is the adapter that sits between them — it translates an incoming Lambda event into an ASGI scope, runs the FastAPI app, and translates the response back into the Lambda format.

Terraform IaC vs Manual AWS Console

Every resource in this project was defined in main.tf and variables.tf. This means the entire infrastructure is version-controlled, reproducible, and can be torn down or redeployed in seconds. Your cloud environment becomes code you can commit, review, and share — rather than manual clicks in a console that nobody can audit or reproduce.


Quick Reference Cheat Sheet

Start Session

# Terminal 1 — Start Floci
docker run --rm -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock floci/floci:latest

# Terminal 3 — Verify
curl http://localhost:4566/_floci/health

# Terminal 2 — Deploy
cd ~/WrestleDream/WrestleDream/terraform && terraform apply -auto-approve

Rebuild and Redeploy After Code Changes

# Terminal 2
cd ~/WrestleDream/WrestleDream
pip cache purge
bash terraform/package_lambda.sh
cd terraform && terraform apply -auto-approve

Open in Browser

# Terminal 2
cd ~/WrestleDream/WrestleDream/frontend
WRESTLEDREAM_API="http://localhost:4566/restapis/<api_id>/dev/_user_request_" \
  npm run dev -- --config vite.config.dev.js
# Open: http://localhost:5173

Tear Down

# Terminal 2
cd ~/WrestleDream/WrestleDream/terraform && terraform destroy -auto-approve
# Terminal 1: Ctrl+C

Going Live on Real AWS

Only two changes needed in terraform/main.tf:

  1. Remove the endpoints {} block from the provider
  2. Remove the fake access_key / secret_key lines
  3. Set your real profile: export AWS_PROFILE=your-real-profile
  4. Run terraform apply — identical infrastructure, real AWS

The Lambda zip packaging, Terraform resources, and all commands remain exactly the same. Floci is a drop-in local replacement for real AWS.


Built by Paris | Floci + Terraform + Docker | May 2026

About

Local AWS cloud emulation with Floci and Terraform infrastructure as code.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors