293 lines
8.7 KiB
Markdown
293 lines
8.7 KiB
Markdown
|
|
---
|
||
|
|
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>
|