docs(01-infrastructure-and-deployment): create phase plan

This commit is contained in:
Viktor Barzin 2026-02-23 22:28:54 +00:00
parent 909c28cf4b
commit d57185e262
3 changed files with 413 additions and 2 deletions

View file

@ -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 | - |

View file

@ -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"
---
<objective>
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.
</objective>
<execution_context>
@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md
@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Create FastAPI backend application with health endpoint</name>
<files>stacks/f1-stream/files/backend/main.py, stacks/f1-stream/files/backend/requirements.txt</files>
<action>
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.
</action>
<verify>
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.
</verify>
<done>
`backend/main.py` exists with a valid FastAPI app that has `/health` and `/` endpoints. `requirements.txt` lists fastapi and uvicorn.
</done>
</task>
<task type="auto">
<name>Task 2: Create Dockerfile and build/push the container image</name>
<files>stacks/f1-stream/files/Dockerfile, stacks/f1-stream/files/redeploy.sh</files>
<action>
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/.
</action>
<verify>
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.
</verify>
<done>
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.
</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/01-infrastructure-and-deployment/01-01-SUMMARY.md`
</output>

View file

@ -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"
---
<objective>
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.
</objective>
<execution_context>
@/Users/viktorbarzin/.claude/get-shit-done/workflows/execute-plan.md
@/Users/viktorbarzin/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Update Terraform deployment for Python/FastAPI and verify NFS mount</name>
<files>stacks/f1-stream/main.tf</files>
<action>
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.
</action>
<verify>
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)
</verify>
<done>
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.
</done>
</task>
<task type="auto">
<name>Task 2: Create Woodpecker CI pipeline for f1-stream</name>
<files>.woodpecker/f1-stream.yml</files>
<action>
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)
</action>
<verify>
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.
</verify>
<done>
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.
</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/01-infrastructure-and-deployment/01-02-SUMMARY.md`
</output>