diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 4207f9f2..37eb0e87 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -38,7 +38,10 @@ Decimal phases appear between their surrounding integers in numeric order.
2. The Terragrunt stack applies cleanly from a fresh checkout with no manual cluster intervention
3. The NFS volume is mounted inside the running pod and a file written to it survives a pod restart
4. Woodpecker CI pipeline exists and triggers on push to the service's directory
-**Plans**: TBD
+**Plans**: 2 plans
+Plans:
+- [ ] 01-01-PLAN.md — Create FastAPI backend app, Dockerfile, and build/push Docker image
+- [ ] 01-02-PLAN.md — Update Terraform deployment, apply stack, verify NFS, add CI pipeline
### Phase 2: F1 Schedule Subsystem
**Goal**: The system automatically fetches the full F1 race calendar and serves it as structured data — users can see all sessions for the current season with correct times.
@@ -125,7 +128,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
-| 1. Infrastructure and Deployment | 0/TBD | Not started | - |
+| 1. Infrastructure and Deployment | 0/2 | Planning complete | - |
| 2. F1 Schedule Subsystem | 0/TBD | Not started | - |
| 3. Extractor Framework and First Site | 0/TBD | Not started | - |
| 4. Stream Health and Fallback | 0/TBD | Not started | - |
diff --git a/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md b/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md
new file mode 100644
index 00000000..a767fc80
--- /dev/null
+++ b/.planning/phases/01-infrastructure-and-deployment/01-01-PLAN.md
@@ -0,0 +1,173 @@
+---
+phase: 01-infrastructure-and-deployment
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - stacks/f1-stream/files/backend/main.py
+ - stacks/f1-stream/files/backend/requirements.txt
+ - stacks/f1-stream/files/Dockerfile
+ - stacks/f1-stream/files/redeploy.sh
+autonomous: true
+requirements:
+ - DEPL-01
+
+must_haves:
+ truths:
+ - "A Docker image viktorbarzin/f1-stream:v2.0.0 exists and can be pulled"
+ - "The image starts a FastAPI server on port 8000 that responds to GET /health with 200"
+ - "The image is based on python:3.13-slim-bookworm and runs without errors"
+ artifacts:
+ - path: "stacks/f1-stream/files/backend/main.py"
+ provides: "FastAPI app with health endpoint"
+ contains: "/health"
+ - path: "stacks/f1-stream/files/backend/requirements.txt"
+ provides: "Python dependencies"
+ contains: "fastapi"
+ - path: "stacks/f1-stream/files/Dockerfile"
+ provides: "Multi-stage Docker build for Python FastAPI"
+ contains: "python:3.13-slim-bookworm"
+ - path: "stacks/f1-stream/files/redeploy.sh"
+ provides: "Build, push, restart script"
+ contains: "docker build"
+ key_links:
+ - from: "stacks/f1-stream/files/Dockerfile"
+ to: "stacks/f1-stream/files/backend/main.py"
+ via: "COPY backend/ into image"
+ pattern: "COPY.*backend"
+ - from: "stacks/f1-stream/files/Dockerfile"
+ to: "stacks/f1-stream/files/backend/requirements.txt"
+ via: "pip install requirements"
+ pattern: "pip install.*requirements"
+---
+
+
+Create a minimal FastAPI backend application with a health endpoint and build a Docker image for it. This replaces the existing Go-based f1-stream application with the new Python/FastAPI stack.
+
+Purpose: Provide a deployable container image that the Terraform stack (Plan 02) will reference. The health endpoint proves the service is running correctly.
+Output: Docker image `viktorbarzin/f1-stream:v2.0.0` pushed to Docker Hub, containing a working FastAPI server.
+
+
+
+@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md
+@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/research/STACK.md
+
+# Existing files to replace/modify:
+@stacks/f1-stream/files/Dockerfile
+@stacks/f1-stream/files/redeploy.sh
+
+
+
+
+
+ Task 1: Create FastAPI backend application with health endpoint
+ stacks/f1-stream/files/backend/main.py, stacks/f1-stream/files/backend/requirements.txt
+
+Create the directory `stacks/f1-stream/files/backend/`.
+
+Create `stacks/f1-stream/files/backend/requirements.txt` with pinned versions:
+```
+fastapi==0.132.0
+uvicorn[standard]
+```
+
+Create `stacks/f1-stream/files/backend/main.py` with a minimal FastAPI application:
+- Import FastAPI
+- Create app instance with title "F1 Streams"
+- Add `GET /health` endpoint that returns `{"status": "ok"}`
+- Add `GET /` root endpoint that returns `{"service": "f1-streams", "version": "2.0.0"}`
+- Add an `if __name__ == "__main__"` block that runs uvicorn on host 0.0.0.0 port 8000
+
+This is intentionally minimal -- just enough to prove the deployment works. Later phases will add schedule, extractor, and proxy routes.
+
+Do NOT add any other dependencies or routes beyond the health check and root. Keep it simple.
+
+
+Run `python3 -c "import ast; ast.parse(open('stacks/f1-stream/files/backend/main.py').read()); print('Syntax OK')"` to verify the Python file is valid.
+Verify requirements.txt exists and contains fastapi and uvicorn.
+
+
+`backend/main.py` exists with a valid FastAPI app that has `/health` and `/` endpoints. `requirements.txt` lists fastapi and uvicorn.
+
+
+
+
+ Task 2: Create Dockerfile and build/push the container image
+ stacks/f1-stream/files/Dockerfile, stacks/f1-stream/files/redeploy.sh
+
+Replace the existing Go Dockerfile at `stacks/f1-stream/files/Dockerfile` with a Python-based Dockerfile:
+
+```dockerfile
+FROM python:3.13-slim-bookworm
+
+WORKDIR /app
+
+COPY backend/requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY backend/ ./backend/
+
+EXPOSE 8000
+
+CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
+```
+
+Key points:
+- Single-stage build (no build stage needed for Python -- interpreted language)
+- Use `python:3.13-slim-bookworm` as base (from stack research)
+- Install deps first for Docker layer caching
+- Expose port 8000 (FastAPI default, different from old Go app's 8080)
+- Run via uvicorn pointing to `backend.main:app`
+
+Update `stacks/f1-stream/files/redeploy.sh`:
+```bash
+#!/usr/bin/env bash
+set -e
+
+docker build -t viktorbarzin/f1-stream:v2.0.0 -t viktorbarzin/f1-stream:latest .
+docker push viktorbarzin/f1-stream:v2.0.0
+docker push viktorbarzin/f1-stream:latest
+kubectl -n f1-stream rollout restart deployment f1-stream
+```
+
+Then build and push the image by running the redeploy script from the `stacks/f1-stream/files/` directory. Only run the docker build and push steps (skip the kubectl rollout -- that happens after Terraform apply in Plan 02).
+
+Build command: `cd stacks/f1-stream/files && docker build -t viktorbarzin/f1-stream:v2.0.0 -t viktorbarzin/f1-stream:latest . && docker push viktorbarzin/f1-stream:v2.0.0 && docker push viktorbarzin/f1-stream:latest`
+
+IMPORTANT: The old Go application files (main.go, go.mod, go.sum, internal/, node_modules/, package.json, package-lock.json, index.html, static/) should be removed from `stacks/f1-stream/files/` since they are no longer needed. Keep only: Dockerfile, redeploy.sh, and backend/.
+
+
+Run `docker images | grep f1-stream` to confirm the image was built.
+Run `docker run --rm -d -p 18000:8000 --name f1-test viktorbarzin/f1-stream:v2.0.0 && sleep 2 && curl -s http://localhost:18000/health && docker stop f1-test` to verify the container starts and the health endpoint responds.
+
+
+Docker image `viktorbarzin/f1-stream:v2.0.0` is built, pushed to Docker Hub, and serves a 200 response on GET /health when run locally.
+
+
+
+
+
+
+1. `docker images | grep f1-stream` shows the v2.0.0 tag
+2. Running the image locally and curling /health returns `{"status": "ok"}`
+3. The old Go files have been removed from `stacks/f1-stream/files/`
+4. Only Dockerfile, redeploy.sh, and backend/ remain in the files directory
+
+
+
+- Docker image viktorbarzin/f1-stream:v2.0.0 exists on Docker Hub
+- The image runs a FastAPI server on port 8000 with a working /health endpoint
+- Old Go application files are cleaned up
+
+
+
diff --git a/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md b/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md
new file mode 100644
index 00000000..bd789069
--- /dev/null
+++ b/.planning/phases/01-infrastructure-and-deployment/01-02-PLAN.md
@@ -0,0 +1,235 @@
+---
+phase: 01-infrastructure-and-deployment
+plan: 02
+type: execute
+wave: 2
+depends_on:
+ - "01-01"
+files_modified:
+ - stacks/f1-stream/main.tf
+ - .woodpecker/f1-stream.yml
+autonomous: true
+requirements:
+ - DEPL-01
+ - DEPL-02
+
+must_haves:
+ truths:
+ - "A request to https://f1.viktorbarzin.me/health returns HTTP 200 with JSON {status: ok}"
+ - "The Terragrunt stack applies cleanly with no errors"
+ - "A file written to /data inside the pod survives a pod restart"
+ - "Woodpecker CI pipeline triggers on push for the f1-stream directory"
+ artifacts:
+ - path: "stacks/f1-stream/main.tf"
+ provides: "Kubernetes deployment, service, ingress, TLS for f1-stream"
+ contains: "viktorbarzin/f1-stream:v2.0.0"
+ - path: ".woodpecker/f1-stream.yml"
+ provides: "CI pipeline for f1-stream service"
+ contains: "f1-stream"
+ key_links:
+ - from: "stacks/f1-stream/main.tf"
+ to: "Docker Hub viktorbarzin/f1-stream:v2.0.0"
+ via: "kubernetes_deployment image reference"
+ pattern: "viktorbarzin/f1-stream:v2.0.0"
+ - from: "stacks/f1-stream/main.tf"
+ to: "NFS /mnt/main/f1-stream"
+ via: "inline NFS volume mount"
+ pattern: "/mnt/main/f1-stream"
+ - from: "stacks/f1-stream/main.tf"
+ to: "modules/kubernetes/ingress_factory"
+ via: "ingress module call"
+ pattern: "ingress_factory"
+---
+
+
+Update the Terraform stack to deploy the new Python/FastAPI container, verify NFS mount persistence, and add a Woodpecker CI pipeline. This completes Phase 1 by making the service live on the cluster and reachable at its public URL.
+
+Purpose: The service must be running on the Kubernetes cluster, reachable at f1.viktorbarzin.me, with NFS storage mounted and CI/CD in place -- ready for application development in Phase 2.
+Output: Live deployment at f1.viktorbarzin.me, NFS-backed persistent storage, Woodpecker CI pipeline.
+
+
+
+@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md
+@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/01-infrastructure-and-deployment/01-01-SUMMARY.md
+
+# Key reference files:
+@stacks/f1-stream/main.tf
+@stacks/f1-stream/terragrunt.hcl
+@.woodpecker/build-cli.yml
+@.woodpecker/default.yml
+
+
+
+
+
+ Task 1: Update Terraform deployment for Python/FastAPI and verify NFS mount
+ stacks/f1-stream/main.tf
+
+Modify `stacks/f1-stream/main.tf` to update the deployment for the new Python/FastAPI application:
+
+1. **Change the container image** from `viktorbarzin/f1-stream:v1.3.1` to `viktorbarzin/f1-stream:v2.0.0`
+
+2. **Change the container port** from 8080 to 8000 (FastAPI/uvicorn default)
+
+3. **Update the service target_port** from 8080 to 8000
+
+4. **Remove old Go-specific environment variables** that are no longer needed:
+ - Remove `WEBAUTHN_RPID`
+ - Remove `WEBAUTHN_ORIGIN`
+ - Remove `WEBAUTHN_DISPLAY_NAME`
+ - Remove `HEADLESS_EXTRACT_ENABLED`
+ - Remove `TURN_URL`
+ - Remove `TURN_SHARED_SECRET`
+ - Remove `TURN_INTERNAL_URL`
+
+5. **Remove unused variables** from the top of the file:
+ - Remove `variable "coturn_turn_secret"` (was for WebRTC/TURN)
+ - Remove `variable "public_ip"` (was for TURN URL)
+ - Keep `variable "tls_secret_name"` and `variable "nfs_server"` (still needed)
+
+6. **Keep the NFS volume mount** exactly as-is -- it already follows the inline NFS pattern:
+ ```hcl
+ volume {
+ name = "data"
+ nfs {
+ server = var.nfs_server
+ path = "/mnt/main/f1-stream"
+ }
+ }
+ ```
+ The volume_mount at `/data` stays the same.
+
+7. **Update resource limits** for Python:
+ ```hcl
+ resources {
+ limits = {
+ cpu = "500m"
+ memory = "256Mi"
+ }
+ requests = {
+ cpu = "50m"
+ memory = "64Mi"
+ }
+ }
+ ```
+ Python/FastAPI with uvicorn needs less CPU than Go+Chromium but similar memory.
+
+8. **Keep everything else unchanged**: namespace, service, tls_secret module, ingress module.
+
+After editing, apply the Terraform stack:
+```bash
+cd stacks/f1-stream && terragrunt apply --non-interactive
+```
+
+Wait for the deployment to roll out:
+```bash
+kubectl --kubeconfig $(pwd)/config -n f1-stream rollout status deployment/f1-stream --timeout=120s
+```
+
+Verify the pod is running:
+```bash
+kubectl --kubeconfig $(pwd)/config -n f1-stream get pods
+```
+
+Verify the health endpoint responds through the public URL:
+```bash
+curl -s https://f1.viktorbarzin.me/health
+```
+
+Verify NFS mount persistence by writing a test file, restarting the pod, and reading it back:
+```bash
+POD=$(kubectl --kubeconfig $(pwd)/config -n f1-stream get pods -l app=f1-stream -o jsonpath='{.items[0].metadata.name}')
+kubectl --kubeconfig $(pwd)/config -n f1-stream exec $POD -- sh -c 'echo "nfs-test-$(date +%s)" > /data/test-file.txt && cat /data/test-file.txt'
+kubectl --kubeconfig $(pwd)/config -n f1-stream rollout restart deployment/f1-stream
+kubectl --kubeconfig $(pwd)/config -n f1-stream rollout status deployment/f1-stream --timeout=120s
+NEW_POD=$(kubectl --kubeconfig $(pwd)/config -n f1-stream get pods -l app=f1-stream -o jsonpath='{.items[0].metadata.name}')
+kubectl --kubeconfig $(pwd)/config -n f1-stream exec $NEW_POD -- cat /data/test-file.txt
+```
+The test file should contain the same content after the pod restart.
+
+
+1. `terragrunt apply` exits with 0 (no errors)
+2. `kubectl get pods -n f1-stream` shows 1/1 Running
+3. `curl -s https://f1.viktorbarzin.me/health` returns `{"status":"ok"}`
+4. NFS persistence test passes (file survives pod restart)
+
+
+The f1-stream deployment is running on the cluster with the new Python/FastAPI image, reachable at https://f1.viktorbarzin.me/health, and the NFS volume at /data persists data across pod restarts.
+
+
+
+
+ Task 2: Create Woodpecker CI pipeline for f1-stream
+ .woodpecker/f1-stream.yml
+
+Create `.woodpecker/f1-stream.yml` following the pattern from `build-cli.yml`:
+
+```yaml
+when:
+ event: push
+ path:
+ include:
+ - "stacks/f1-stream/files/**"
+
+clone:
+ git:
+ image: woodpeckerci/plugin-git
+ settings:
+ attempts: 5
+ backoff: 10s
+
+steps:
+ - name: build-image
+ image: woodpeckerci/plugin-docker-buildx
+ settings:
+ username: "viktorbarzin"
+ password:
+ from_secret: dockerhub-pat
+ repo: viktorbarzin/f1-stream
+ dockerfile: stacks/f1-stream/files/Dockerfile
+ context: stacks/f1-stream/files
+ auto_tag: true
+```
+
+Key differences from the default pipeline:
+- **Path filter**: Only triggers when files under `stacks/f1-stream/files/` change (the application code)
+- **Builds and pushes the Docker image** using the same `woodpeckerci/plugin-docker-buildx` pattern as build-cli.yml
+- **Docker context** points to the `stacks/f1-stream/files/` directory where the Dockerfile lives
+- Does NOT run Terragrunt apply (that is done manually or by the default pipeline for the platform stack)
+
+
+Verify the YAML is valid: `python3 -c "import yaml; yaml.safe_load(open('.woodpecker/f1-stream.yml')); print('YAML OK')"`
+Verify the file exists and references f1-stream correctly.
+
+
+Woodpecker CI pipeline file exists at `.woodpecker/f1-stream.yml`, configured to build and push the Docker image when files under `stacks/f1-stream/files/` change.
+
+
+
+
+
+
+1. `curl -s https://f1.viktorbarzin.me/health` returns `{"status":"ok"}`
+2. `cd stacks/f1-stream && terragrunt plan --non-interactive` shows no changes (stack is clean)
+3. NFS test file written before pod restart is readable after pod restart
+4. `.woodpecker/f1-stream.yml` exists and is valid YAML
+5. `kubectl --kubeconfig $(pwd)/config -n f1-stream get pods` shows 1/1 Running
+
+
+
+- The service is live at https://f1.viktorbarzin.me and responds with 200 on /health
+- Terragrunt stack applies cleanly with no manual cluster intervention
+- NFS volume mount at /data persists data across pod restarts
+- Woodpecker CI pipeline exists for automated image builds
+
+
+