docs(01): create phase 1 foundation plans
Phase 01: Foundation - 2 plans in 2 waves - Plan 01: Go project + Dockerfile (wave 1) - Plan 02: Dev environment + verification (wave 2) - Ready for execution Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
292
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
292
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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).
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/acty/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/acty/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create Docker Compose development environment</name>
|
||||
<files>
|
||||
docker-compose.yml
|
||||
.air.toml
|
||||
</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>
|
||||
```bash
|
||||
docker compose config && echo "Compose config valid"
|
||||
```
|
||||
</verify>
|
||||
<done>
|
||||
- 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`
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Verify container isolation and health endpoint</name>
|
||||
<files></files>
|
||||
<action>
|
||||
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 .
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
```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 <action>"
|
||||
```
|
||||
</verify>
|
||||
<done>
|
||||
- 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
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
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
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
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.
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if dev environment works, or describe any issues</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- `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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user