diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 9b8f1ab..abbb17b 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -24,6 +24,12 @@
- **INFRA-01**: Docker container runs isolated to mounted volume only
- **INFRA-02**: Single binary Go backend
+**Plans:** 2 plans
+
+Plans:
+- [ ] 01-01-PLAN.md — Go project structure and multi-stage Dockerfile
+- [ ] 01-02-PLAN.md — Docker Compose dev environment and verification
+
**Success Criteria:**
1. Docker container starts with volume mount and runs Go binary
2. Container cannot access files outside mounted volume
@@ -141,7 +147,7 @@
| Phase | Status | Requirements Covered |
|-------|--------|---------------------|
-| 1 - Foundation | Pending | 2/21 (10%) |
+| 1 - Foundation | Planned | 2/21 (10%) |
| 2 - CLI Tool | Pending | 4/21 (19%) |
| 3 - Authentication | Pending | 4/21 (19%) |
| 4 - Core File Operations | Pending | 4/21 (19%) |
@@ -152,7 +158,7 @@
- **Total v1 requirements:** 21
- **Mapped:** 21
-- **Unmapped:** 0 ✓
+- **Unmapped:** 0
### Coverage Map
diff --git a/.planning/phases/01-foundation/01-01-PLAN.md b/.planning/phases/01-foundation/01-01-PLAN.md
new file mode 100644
index 0000000..e11d740
--- /dev/null
+++ b/.planning/phases/01-foundation/01-01-PLAN.md
@@ -0,0 +1,202 @@
+---
+phase: 01-foundation
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - go.mod
+ - go.sum
+ - cmd/server/main.go
+ - internal/health/handler.go
+ - docker/Dockerfile
+ - .dockerignore
+autonomous: true
+
+must_haves:
+ truths:
+ - "Go module initializes without errors"
+ - "Go server compiles to a single binary"
+ - "Docker image builds successfully for linux/amd64"
+ - "Built image is less than 150MB (debian slim + static binary)"
+ artifacts:
+ - path: "go.mod"
+ provides: "Go module definition"
+ contains: "module"
+ - path: "cmd/server/main.go"
+ provides: "Application entry point"
+ contains: "func main()"
+ - path: "internal/health/handler.go"
+ provides: "Health check endpoint handler"
+ contains: "func Handler"
+ - path: "docker/Dockerfile"
+ provides: "Multi-stage build definition"
+ contains: "FROM golang"
+ - path: ".dockerignore"
+ provides: "Build context exclusions"
+ contains: ".git"
+ key_links:
+ - from: "cmd/server/main.go"
+ to: "internal/health"
+ via: "import statement"
+ pattern: "internal/health"
+ - from: "docker/Dockerfile"
+ to: "cmd/server"
+ via: "go build command"
+ pattern: "go build.*cmd/server"
+---
+
+
+Create Go project structure and multi-stage Dockerfile for isolated container builds.
+
+Purpose: Establishes the foundational artifacts needed to build and run the Pirate Station backend. This plan creates the Go module with a minimal HTTP server and the multi-stage Dockerfile that produces a portable ARM64/x86_64 binary.
+
+Output: Buildable Go project with Dockerfile that produces a slim container image.
+
+
+
+@/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
+
+
+
+
+
+ Task 1: Initialize Go project with HTTP server
+
+ go.mod
+ go.sum
+ cmd/server/main.go
+ internal/health/handler.go
+
+
+1. Initialize Go module:
+ ```bash
+ go mod init github.com/acty/pirate-station
+ ```
+ (Use github.com path even though pushing to Gitea - conventional Go module naming)
+
+2. Create cmd/server/main.go with:
+ - Import net/http and internal/health
+ - http.HandleFunc for "/" returning "Pirate Station API"
+ - http.HandleFunc for "/health" using health.Handler
+ - ListenAndServe on :32768
+ - log.Fatal on server error
+ - Log startup message with port number
+
+3. Create internal/health/handler.go with:
+ - package health
+ - Handler(w http.ResponseWriter, r *http.Request) function
+ - Check os.Stat("/data") - return 503 if not exists
+ - Return 200 with {"status":"healthy"} JSON if /data exists
+ - Return 503 with {"status":"unhealthy","reason":"data volume not mounted"} if not
+
+4. Run `go mod tidy` to create go.sum
+
+Keep it minimal - no frameworks, no external dependencies, pure stdlib.
+
+
+ ```bash
+ go build -o /tmp/server ./cmd/server && echo "Build successful"
+ ```
+
+
+ - go.mod exists with module path
+ - cmd/server/main.go compiles
+ - internal/health/handler.go provides health check
+ - No external dependencies (only stdlib)
+
+
+
+
+ Task 2: Create multi-stage Dockerfile
+
+ docker/Dockerfile
+ .dockerignore
+
+
+1. Create docker/Dockerfile with multi-stage build:
+
+ Build stage:
+ - FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS builder
+ - WORKDIR /build
+ - COPY go.mod go.sum ./ (cache dependencies separately)
+ - RUN go mod download
+ - COPY . .
+ - ARG TARGETOS TARGETARCH
+ - RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /server ./cmd/server
+
+ Runtime stage:
+ - FROM debian:bookworm-slim
+ - RUN useradd -u 10001 -m appuser
+ - USER appuser
+ - COPY --from=builder /server /usr/local/bin/server
+ - VOLUME /data
+ - EXPOSE 32768
+ - CMD ["server"]
+
+2. Create .dockerignore at project root:
+ ```
+ .git
+ .gitignore
+ README.md
+ *.md
+ .env
+ .env.local
+ .DS_Store
+ .air.toml
+ docker-compose.yml
+ .planning/
+ tmp/
+ ```
+
+Key points:
+- Use --platform=$BUILDPLATFORM for native build speed
+- CGO_ENABLED=0 for static binary (no libc dependency)
+- -ldflags="-w -s" strips debug symbols (smaller binary)
+- Non-root user (appuser) for security
+- VOLUME /data declares mount point
+- Port 32768 as per user decision
+
+
+ ```bash
+ docker build -f docker/Dockerfile -t pirate-station:test . && docker images pirate-station:test --format "{{.Size}}"
+ ```
+ Image should build and be under 150MB.
+
+
+ - docker/Dockerfile exists with multi-stage build
+ - .dockerignore excludes non-essential files
+ - Image builds successfully
+ - Image size under 150MB (debian slim + Go binary)
+
+
+
+
+
+
+1. Go project compiles: `go build ./...`
+2. Docker image builds: `docker build -f docker/Dockerfile -t pirate-station:test .`
+3. Container starts: `docker run --rm -v /tmp/test-data:/data pirate-station:test &` then `curl localhost:32768/health`
+4. Container cannot access host filesystem outside /data mount
+
+
+
+- Go module initialized with github.com/acty/pirate-station
+- HTTP server listens on :32768
+- /health endpoint returns JSON status
+- Dockerfile produces working image under 150MB
+- Image runs as non-root user
+- No external Go dependencies (stdlib only)
+
+
+
diff --git a/.planning/phases/01-foundation/01-02-PLAN.md b/.planning/phases/01-foundation/01-02-PLAN.md
new file mode 100644
index 0000000..40200c1
--- /dev/null
+++ b/.planning/phases/01-foundation/01-02-PLAN.md
@@ -0,0 +1,292 @@
+---
+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
+
+
+
diff --git a/.planning/phases/01-foundation/01-RESEARCH.md b/.planning/phases/01-foundation/01-RESEARCH.md
new file mode 100644
index 0000000..b0f9f26
--- /dev/null
+++ b/.planning/phases/01-foundation/01-RESEARCH.md
@@ -0,0 +1,510 @@
+# Phase 1: Foundation - Research
+
+**Researched:** 2026-02-03
+**Domain:** Go backend containerization with Docker
+**Confidence:** HIGH
+
+## Summary
+
+This phase establishes a Go HTTP backend running in an isolated Docker container with volume-mounted storage. The standard approach uses multi-stage Dockerfiles with golang:latest for building and debian:slim for runtime, deployed via Docker Compose for development and direct builds on the Raspberry Pi.
+
+The research validates all locked decisions from the context discussion: Debian slim provides better compatibility than Alpine for cross-platform development, buildx handles multi-arch builds (x86_64 + ARM64) seamlessly, and Docker Compose with bind mounts enables fast iteration during local development. The high port (32768) avoids common service conflicts, and standard volume isolation provides adequate security without filesystem hardening overhead.
+
+Current Go stable versions are 1.24 and 1.25 (Go 1.26 releases February 2026). Official Go project layout guidance recommends `internal/` for private packages and `cmd/` for multiple commands, with module paths matching repository URLs.
+
+**Primary recommendation:** Use multi-stage Dockerfile with Go 1.25+, debian:slim base, buildx for multi-arch, and Air for development hot reload. Follow official Go module layout with `cmd/` and `internal/` directories.
+
+## Standard Stack
+
+The established libraries/tools for this domain:
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Go | 1.25+ | Backend language | Latest stable (1.24/1.25 supported) |
+| Docker Buildx | bundled | Multi-arch builds | Official Docker tool for cross-platform |
+| Docker Compose | v2 | Dev orchestration | Standard for local development workflows |
+| golang:1.25 | latest stable | Build stage base | Official Go image with full toolchain |
+| debian:slim | 12 (bookworm) | Runtime base | Balance of size, compatibility, debugging |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| Air | latest | Hot reload | Development only - NOT for production |
+| gcr.io/distroless/base | latest | Alternative runtime | When minimal image size is critical |
+| alpine:latest | 3.19+ | Alternative runtime | When musl libc compatibility is acceptable |
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| debian:slim | gcr.io/distroless/base | Smaller (~20MB vs ~80MB) but no shell for debugging |
+| debian:slim | alpine:latest | Smaller (~5MB) but musl libc can cause unexpected behavior with net package |
+| Manual builds | GitHub Actions | Automated but adds complexity for transitional dev environment |
+
+**Installation:**
+```bash
+# Go toolchain
+# Download from https://go.dev/dl/ or use system package manager
+
+# Docker Buildx (included in Docker Desktop, or install separately)
+docker buildx version
+
+# Air for hot reload (development only)
+go install github.com/cosmtrek/air@latest
+```
+
+## Architecture Patterns
+
+### Recommended Project Structure
+```
+pirate-station/
+├── cmd/
+│ └── server/ # Main application entry point
+│ └── main.go
+├── internal/ # Private application code
+│ ├── server/ # HTTP server setup
+│ ├── storage/ # /data volume operations
+│ └── health/ # Health check handlers
+├── docker/
+│ └── Dockerfile # Multi-stage build definition
+├── docker-compose.yml # Development environment
+├── .air.toml # Air hot reload config
+├── .dockerignore # Exclude from build context
+├── go.mod
+└── go.sum
+```
+
+**Rationale:**
+- `cmd/server/` - Single command, follows official Go layout for commands
+- `internal/` - Prevents external imports, recommended for server packages
+- `docker/` - Separates infrastructure from code
+- Root-level compose file - Standard Docker Compose convention
+
+### Pattern 1: Multi-Stage Dockerfile with Cross-Compilation
+
+**What:** Build stage compiles Go binary, runtime stage creates minimal production image
+**When to use:** Always - reduces image size by 95% and improves security
+
+**Example:**
+```dockerfile
+# Build stage - use native platform for speed
+FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS builder
+
+WORKDIR /build
+
+# Cache dependencies separately
+COPY go.mod go.sum ./
+RUN go mod download
+
+# Copy source and build for target platform
+COPY . .
+ARG TARGETOS TARGETARCH
+RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
+ go build -ldflags="-w -s" -o /server ./cmd/server
+
+# Runtime stage - debian slim for debugging
+FROM debian:bookworm-slim
+
+# Run as non-root
+RUN useradd -u 10001 -m appuser
+USER appuser
+
+COPY --from=builder /server /usr/local/bin/server
+
+# Volume mount point
+VOLUME /data
+
+EXPOSE 32768
+
+CMD ["server"]
+```
+
+**Source:** Official Docker documentation - [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)
+
+### Pattern 2: Docker Compose Development Environment
+
+**What:** Volume-mounted source with hot reload for rapid iteration
+**When to use:** Local development only
+
+**Example:**
+```yaml
+# docker-compose.yml
+services:
+ backend:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile
+ target: builder # Stop at build stage
+ command: air -c .air.toml # Hot reload
+ 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:
+```
+
+**Source:** Docker Compose documentation - [Development best practices](https://docs.docker.com/guides/golang/develop/)
+
+### Pattern 3: Health Check Endpoint
+
+**What:** HTTP endpoint returning 200 when ready, 503 when unhealthy
+**When to use:** Required for production deployments, useful in development
+
+**Example:**
+```go
+// internal/health/handler.go
+package health
+
+import (
+ "net/http"
+ "os"
+)
+
+func Handler(w http.ResponseWriter, r *http.Request) {
+ // Check data volume is accessible
+ if _, err := os.Stat("/data"); os.IsNotExist(err) {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ w.Write([]byte(`{"status":"unhealthy","reason":"data volume not mounted"}`))
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status":"healthy"}`))
+}
+```
+
+**Source:** Patterns from [heptiolabs/healthcheck](https://pkg.go.dev/github.com/heptiolabs/healthcheck) and [Kubernetes health checks guide](https://oneuptime.com/blog/post/2026-01-07-go-health-checks-kubernetes/view)
+
+### Anti-Patterns to Avoid
+
+- **Latest tag in production:** Always pin specific versions (golang:1.25-bookworm not golang:latest)
+- **Single-stage Dockerfile:** Bloats image with build tools, increases attack surface
+- **Root user in container:** Security risk - always create non-root user
+- **Copying unnecessary files:** Use .dockerignore to exclude .git, test files, etc.
+- **Running Air in production:** Hot reload tools are development-only
+
+## Don't Hand-Roll
+
+Problems that look simple but have existing solutions:
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Health check logic | Custom health tracking | Standard /health endpoint pattern | Kubernetes/Docker expect specific format |
+| Hot reload mechanism | File watching + restart script | Air (cosmtrek/air) | Handles Go build errors, configurable, maintained |
+| Build caching | Manual cache dirs | Docker BuildKit layer caching | Optimized for dependencies (go.mod) vs source |
+| Multi-arch builds | Multiple Dockerfiles | docker buildx with --platform | Cross-compiles natively, single source of truth |
+| Static binary linking | Complex ldflags research | CGO_ENABLED=0 + -ldflags="-w -s" | Standard Go approach, strips debug symbols |
+
+**Key insight:** Docker's native tooling (BuildKit, buildx) and Go's built-in cross-compilation handle most complexity. Avoid custom shell scripts for build processes.
+
+## Common Pitfalls
+
+### Pitfall 1: Poor Layer Caching in Dockerfile
+
+**What goes wrong:** Every code change triggers full dependency reinstall, dramatically slowing builds
+
+**Why it happens:** Copying all source files before running `go mod download` invalidates cache when any file changes
+
+**How to avoid:** Copy go.mod and go.sum first, download dependencies, then copy source code
+
+**Warning signs:**
+- "Downloading dependencies" on every build even when go.mod unchanged
+- Multi-minute builds for trivial code changes
+
+**Example:**
+```dockerfile
+# BAD - invalidates cache on any file change
+COPY . .
+RUN go mod download
+
+# GOOD - cache dependencies separately
+COPY go.mod go.sum ./
+RUN go mod download
+COPY . .
+```
+
+**Source:** Docker best practices - [Dockerfile optimization](https://medium.com/@mecreate/dockerizing-a-go-application-a-complete-guide-with-best-practices-5648d4eb362c)
+
+### Pitfall 2: Alpine + net Package DNS Issues
+
+**What goes wrong:** DNS lookups fail or behave unexpectedly when using Alpine Linux base images
+
+**Why it happens:** Alpine uses musl libc instead of glibc, affecting Go's net package behavior unless compiled with CGO_ENABLED=0
+
+**How to avoid:**
+- Use Debian slim instead of Alpine (decision already made)
+- Or ensure CGO_ENABLED=0 for pure Go DNS resolver
+
+**Warning signs:**
+- DNS resolution works locally but fails in Alpine container
+- Intermittent connection errors to external services
+
+**Source:** Go package documentation and community reports - [Alpine vs Debian comparison](https://mohibulalam75.medium.com/exploring-lightweight-docker-base-images-alpine-slim-and-debian-releases-bookworm-bullseye-688f88067f4b)
+
+### Pitfall 3: CGO Enabled by Default in Cross-Compilation
+
+**What goes wrong:** Cross-compiled binaries fail to run on target architecture with "file not found" errors despite binary existing
+
+**Why it happens:** CGO is enabled by default, creating dynamic links to C libraries that may not exist on target system
+
+**How to avoid:** Explicitly set CGO_ENABLED=0 in Dockerfile for static binary compilation
+
+**Warning signs:**
+- Binary works on build machine but fails on target
+- "no such file or directory" error for binary that exists
+- Larger binary size than expected
+
+**Example:**
+```dockerfile
+# REQUIRED for portable binaries
+ARG TARGETOS TARGETARCH
+RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
+ go build -o /server ./cmd/server
+```
+
+**Source:** Official Docker Go guide - [Building Go images](https://docs.docker.com/guides/golang/build-images/)
+
+### Pitfall 4: Forgetting .dockerignore
+
+**What goes wrong:** Huge build contexts, slow builds, sensitive files copied to image
+
+**Why it happens:** Docker copies entire directory context by default, including .git, test data, and local builds
+
+**How to avoid:** Create .dockerignore file early in development
+
+**Warning signs:**
+- "Sending build context" takes seconds
+- Docker build transfers hundreds of MB
+- Accidentally including .env files
+
+**Example .dockerignore:**
+```
+.git
+.gitignore
+README.md
+.env
+.env.local
+*.md
+.DS_Store
+.air.toml
+docker-compose.yml
+.planning/
+```
+
+**Source:** Docker best practices - [Common Dockerfile mistakes](https://runnable.com/blog/9-common-dockerfile-mistakes)
+
+### Pitfall 5: Bind Mounting in Production
+
+**What goes wrong:** Using docker-compose.yml from development in production, exposing host filesystem
+
+**Why it happens:** Not separating development and production configurations
+
+**How to avoid:**
+- Use bind mounts only in development (docker-compose.yml)
+- Use named volumes or direct mounts in production deployment
+- Never bind mount sensitive host directories
+
+**Warning signs:**
+- Host files accessible from production container
+- Container can modify unexpected host paths
+- Security audit flags container filesystem access
+
+**Source:** Docker security guidance - [Volume mount security](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)
+
+## Code Examples
+
+Verified patterns from official sources:
+
+### Minimal HTTP Server with Health Check
+```go
+// cmd/server/main.go
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+)
+
+func main() {
+ http.HandleFunc("/health", healthHandler)
+ http.HandleFunc("/", rootHandler)
+
+ port := ":32768"
+ log.Printf("Server starting on port %s", port)
+ if err := http.ListenAndServe(port, nil); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func healthHandler(w http.ResponseWriter, r *http.Request) {
+ // Verify data volume is mounted
+ if _, err := os.Stat("/data"); os.IsNotExist(err) {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ fmt.Fprintf(w, `{"status":"unhealthy"}`)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `{"status":"healthy"}`)
+}
+
+func rootHandler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Pirate Station API")
+}
+```
+
+**Source:** Standard Go net/http patterns from [go.dev documentation](https://go.dev/doc/)
+
+### Multi-Arch Build Command
+```bash
+# Create builder (one-time setup)
+docker buildx create --name pirate-builder --bootstrap --use
+
+# Build and push multi-arch image
+docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --tag gitea.local/acty/pirate-station:latest \
+ --push \
+ -f docker/Dockerfile \
+ .
+```
+
+**Source:** Official Docker buildx documentation - [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)
+
+### Air Configuration for Hot Reload
+```toml
+# .air.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"]
+ 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
+```
+
+**Source:** Air documentation - [github.com/air-verse/air](https://github.com/air-verse/air)
+
+### Gitea Registry Push
+```bash
+# Login to Gitea container registry
+docker login gitea.local
+
+# Tag image with Gitea format
+docker tag pirate-station:latest gitea.local/acty/pirate-station:latest
+
+# Push to registry
+docker push gitea.local/acty/pirate-station:latest
+```
+
+**Source:** Gitea container registry documentation - [Gitea docs](https://docs.gitea.com/usage/packages/container)
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Alpine for minimal size | Debian slim for compatibility | Ongoing shift 2024-2026 | Better C library compatibility, easier debugging |
+| Manual multi-arch builds | docker buildx with --platform | 2020+ (BuildKit) | Single command for cross-platform |
+| CGO enabled by default | CGO_ENABLED=0 for containers | Standard practice now | True static binaries, better portability |
+| golang:latest tag | golang:1.25-bookworm pinned | Best practice evolution | Reproducible builds |
+| docker build | docker buildx build | 2020+ (BuildKit default) | Better caching, multi-platform support |
+| Scratch runtime base | Distroless or Debian slim | 2022+ trend | Balance of size and debuggability |
+
+**Deprecated/outdated:**
+- **MAINTAINER instruction:** Use LABEL maintainer= instead (deprecated Docker syntax)
+- **Docker Compose v1:** Use v2 with `docker compose` command (not docker-compose)
+- **Go 1.21 and earlier:** Out of support - use 1.24+ (two most recent major releases supported)
+
+## Open Questions
+
+Things that couldn't be fully resolved:
+
+1. **Gitea multi-arch manifest support**
+ - What we know: Gitea supports OCI-compliant container registry
+ - What's unclear: Some GitHub issues report buildx multi-arch push creating "unknown architectures"
+ - Recommendation: Test multi-arch push during phase implementation; fallback to building directly on Pi if issues arise
+
+2. **Air performance in Docker on Pi**
+ - What we know: Air works well for development, not recommended for production
+ - What's unclear: Performance impact of file watching on bind-mounted volumes on Raspberry Pi
+ - Recommendation: Test in development, may fall back to manual rebuild if too slow
+
+3. **Debian slim vs distroless tradeoff**
+ - What we know: User chose Debian slim for debugging, distroless is smaller
+ - What's unclear: How often shell access is needed in practice
+ - Recommendation: Start with Debian slim as decided, can switch to distroless later if shell not needed
+
+## Sources
+
+### Primary (HIGH confidence)
+- Docker official documentation - [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)
+- Docker official documentation - [Building Go images](https://docs.docker.com/guides/golang/build-images/)
+- Go official documentation - [Module layout](https://go.dev/doc/modules/layout)
+- Go official documentation - [Package names](https://go.dev/blog/package-names)
+- Go version status - [endoflife.date/go](https://endoflife.date/go)
+- Gitea container registry - [Official docs](https://docs.gitea.com/usage/packages/container)
+
+### Secondary (MEDIUM confidence)
+- Air hot reload tool - [github.com/air-verse/air](https://github.com/air-verse/air)
+- Go project layout reference - [golang-standards/project-layout](https://github.com/golang-standards/project-layout)
+- Health check patterns - [Kubernetes Go health checks](https://oneuptime.com/blog/post/2026-01-07-go-health-checks-kubernetes/view)
+- Docker best practices - [Dockerizing Go applications](https://medium.com/@mecreate/dockerizing-a-go-application-a-complete-guide-with-best-practices-5648d4eb362c)
+
+### Tertiary (LOW confidence)
+- Gitea multi-arch buildx issues - Community reports from GitHub issues, marked for validation during implementation
+- Alpine vs Debian tradeoffs - Multiple blog posts agree, but requires real-world testing for specific use case
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH - Official documentation and current best practices verified
+- Architecture: HIGH - Official Go and Docker documentation provides clear patterns
+- Pitfalls: HIGH - Well-documented common issues with official solutions
+- Gitea integration: MEDIUM - Official docs available but multi-arch behavior needs testing
+
+**Research date:** 2026-02-03
+**Valid until:** 2026-04-03 (60 days - Docker and Go tooling relatively stable)