--- phase: 01-foundation plan: 02 type: execute wave: 2 depends_on: ["01-01"] files_modified: - docker-compose.yml - .air.toml autonomous: false must_haves: truths: - "Developer can start local environment with single command" - "Code changes trigger automatic rebuild" - "Container can only access /data volume, not host filesystem" - "Health endpoint returns healthy when /data mounted" - "Health endpoint returns unhealthy when /data not mounted" artifacts: - path: "docker-compose.yml" provides: "Development environment orchestration" contains: "services:" - path: ".air.toml" provides: "Hot reload configuration" contains: "[build]" key_links: - from: "docker-compose.yml" to: "docker/Dockerfile" via: "build context" pattern: "dockerfile.*docker/Dockerfile" - from: "docker-compose.yml" to: ".air.toml" via: "air command" pattern: "air.*\\.air\\.toml" --- Set up Docker Compose development environment with hot reload and verify complete foundation. Purpose: Creates the local development workflow so changes are automatically rebuilt. Then verifies the entire Phase 1 deliverable works end-to-end: container isolation, HTTP serving, and health checks. Output: Working development environment with hot reload; verified Phase 1 requirements (INFRA-01, INFRA-02). @/home/acty/.claude/get-shit-done/workflows/execute-plan.md @/home/acty/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.md @.planning/phases/01-foundation/01-01-SUMMARY.md Task 1: Create Docker Compose development environment docker-compose.yml .air.toml 1. Create docker-compose.yml at project root: ```yaml services: backend: build: context: . dockerfile: docker/Dockerfile target: builder # Stop at build stage for dev command: air -c .air.toml ports: - "32768:32768" volumes: - .:/workspace:cached # Bind mount for live editing - data:/data # Named volume for persistent data working_dir: /workspace environment: - CGO_ENABLED=0 volumes: data: ``` 2. Create .air.toml for hot reload: ```toml root = "." testdata_dir = "testdata" tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/server" cmd = "go build -o ./tmp/server ./cmd/server" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", ".planning", "docker", ".git"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] include_ext = ["go", "tpl", "tmpl", "html"] include_file = [] kill_delay = "0s" log = "build-errors.log" poll = false poll_interval = 0 rerun = false rerun_delay = 500 send_interrupt = false stop_on_error = false [color] app = "" build = "yellow" main = "magenta" runner = "green" watcher = "cyan" [log] main_only = false time = false [misc] clean_on_exit = false [screen] clear_on_rebuild = false keep_scroll = true ``` Key points: - target: builder uses build stage (has Go toolchain) - Bind mount .:/workspace for live editing - Named volume data:/data for persistent storage - Air watches for .go file changes and rebuilds ```bash docker compose config && echo "Compose config valid" ``` - docker-compose.yml exists and is valid YAML - .air.toml configures hot reload for Go files - Development environment can be started with `docker compose up` Task 2: Verify container isolation and health endpoint 1. Build production image: ```bash docker build -f docker/Dockerfile -t pirate-station:latest . ``` 2. Test container isolation - verify container cannot access host filesystem: ```bash # Create a test file outside of data volume echo "secret" > /tmp/host-secret.txt # Run container with data volume and try to access host file docker run --rm -d --name isolation-test -p 32768:32768 -v pirate-test-data:/data pirate-station:latest # Container should NOT be able to see /tmp/host-secret.txt # (It's isolated to its own filesystem + /data volume only) ``` 3. Test health endpoint with volume mounted: ```bash # Wait for container to start sleep 2 # Health check should return healthy curl -s http://localhost:32768/health # Expected: {"status":"healthy"} # Root endpoint should return API message curl -s http://localhost:32768/ # Expected: Pirate Station API # Stop test container docker stop isolation-test docker volume rm pirate-test-data ``` 4. Test health endpoint WITHOUT volume (should be unhealthy): ```bash # Run container without /data volume mounted docker run --rm -d --name no-data-test -p 32769:32768 pirate-station:latest sleep 2 # Health check should return unhealthy curl -s http://localhost:32769/health # Expected: {"status":"unhealthy","reason":"data volume not mounted"} docker stop no-data-test ``` 5. Verify multi-arch build capability (don't push, just verify it works): ```bash # Create builder if not exists docker buildx create --name pirate-builder --use 2>/dev/null || docker buildx use pirate-builder # Build for both platforms (local only, no push) docker buildx build --platform linux/amd64,linux/arm64 -f docker/Dockerfile -t pirate-station:multiarch . ``` ```bash # All tests pass if: # 1. curl localhost:32768/health returns {"status":"healthy"} # 2. curl localhost:32769/health returns unhealthy # 3. buildx multi-arch build completes echo "Verification requires running the tests in " ``` - Production image builds successfully - Container isolation verified (cannot access host filesystem) - Health endpoint returns healthy with /data mounted - Health endpoint returns unhealthy without /data - Multi-arch build works for amd64 and arm64 Complete Phase 1 foundation: - Go HTTP server on port 32768 - Multi-stage Dockerfile with Debian slim runtime - Docker Compose dev environment with hot reload - Health check endpoint that verifies /data volume 1. Start dev environment: `docker compose up --build` 2. In another terminal, test endpoints: - `curl http://localhost:32768/` should return "Pirate Station API" - `curl http://localhost:32768/health` should return {"status":"healthy"} 3. Edit cmd/server/main.go (change the root message text) 4. Watch terminal - Air should detect change and rebuild 5. `curl http://localhost:32768/` should show your change 6. Ctrl+C to stop, then `docker compose down` Expected: Server starts, endpoints respond, hot reload works. Type "approved" if dev environment works, or describe any issues Phase 1 requirements verification: **INFRA-01: Docker container runs isolated to mounted volume only** - Container uses non-root user (appuser) - Only /data volume is mounted from host - Container filesystem is isolated (debian slim base) - Verified by: Cannot access host files outside /data **INFRA-02: Single binary Go backend** - Go compiles to single static binary (CGO_ENABLED=0) - Binary includes all dependencies (stdlib only) - No runtime dependencies except libc (debian slim provides) - Verified by: `go build ./cmd/server` produces single executable **Success criteria from roadmap:** 1. Docker container starts with volume mount and runs Go binary - VERIFIED 2. Container cannot access files outside mounted volume - VERIFIED 3. Go backend serves HTTP endpoint on specified port (32768) - VERIFIED 4. Container can be built on x86 and deployed to ARM64 (Pi) - VERIFIED via buildx - `docker compose up` starts dev environment - Hot reload rebuilds on code changes - `curl localhost:32768/health` returns healthy JSON - Production build works for amd64 and arm64 - Container isolation verified (no host filesystem access beyond /data) - Human verification confirms dev workflow works After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`