phase: 01-docker-compose-stack
plan: 02
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- ollama-entrypoint.sh
- docker-compose.yml
- logs/test.log
- smoke_test.sh
autonomous: true
requirements:
- SVC-03
- SVC-04
- SVC-06
- ING-04
- OPS-03

must_haves:
truths:
- "docker compose up starts all three services (app, db, ollama)"
- "Ollama auto-pulls nomic-embed-text on first startup"
- "pgvector data persists across container restarts via named volume"
- "App waits for DB and Ollama to be healthy before starting"
- "Logs are visible via docker compose logs"
artifacts:
- path: "ollama-entrypoint.sh"
provides: "Ollama startup script with model auto-pull"
contains: "ollama pull nomic-embed-text"
- path: "docker-compose.yml"
provides: "Three-service stack definition"
contains: "pgvector/pgvector:pg17"
- path: "logs/test.log"
provides: "Sample log fixture for integration testing"
min_lines: 5
- path: "smoke_test.sh"
provides: "Automated smoke test sequence for all requirements"
contains: "curl"
key_links:
- from: "docker-compose.yml (app service)"
to: "Dockerfile"
via: "build: ."
pattern: "build:\s*\."
- from: "docker-compose.yml (db service)"
to: "schema.sql"
via: "volume mount to /docker-entrypoint-initdb.d/"
pattern: "schema.sql:/docker-entrypoint-initdb.d"
- from: "docker-compose.yml (ollama service)"
to: "ollama-entrypoint.sh"
via: "bind mount as entrypoint script"
pattern: "ollama-entrypoint.sh"
- from: "docker-compose.yml (app service)"
to: ".env"
via: "env_file directive"
pattern: "env_file"



Create docker-compose.yml with all three services (app, db, ollama), the Ollama entrypoint script for model auto-pull, a test log fixture, and a smoke test script.

Purpose: This is the core infrastructure plan -- it wires together the Dockerfile from Plan 01 with PostgreSQL+pgvector and Ollama into a single docker compose up command.
Output: docker-compose.yml, ollama-entrypoint.sh, logs/test.log, smoke_test.sh


@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md


@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-docker-compose-stack/01-RESEARCH.md
@.planning/phases/01-docker-compose-stack/01-01-SUMMARY.md

@Dockerfile
@.env.example
@schema.sql
@api.py

Dockerfile: FROM python:3.14-slim, WORKDIR /app, EXPOSE 8080, CMD uvicorn api:app --host 0.0.0.0 --port 8080
.env.example: DATABASE_URL=postgresql://rag:rag@db:5432/supportdesk, OLLAMA_URL=http://ollama:11434, POSTGRES_USER=rag, POSTGRES_PASSWORD=rag, POSTGRES_DB=supportdesk
requirements.txt: psycopg2-binary==2.9.11, pgvector==0.4.2
schema.sql: CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE IF NOT EXISTS chunks(...)


Task 1: Create ollama-entrypoint.sh with model auto-pull
ollama-entrypoint.sh

- .planning/phases/01-docker-compose-stack/01-RESEARCH.md (Pattern 2: Ollama Model Auto-Pull via Entrypoint Script, lines 128-152)


Create ollama-entrypoint.sh in the repo root with the following exact content:

```sh
#!/bin/sh
set -e

# Start Ollama server in background
ollama serve &
SERVE_PID=$!

# Wait for Ollama API to be responsive
echo "Waiting for Ollama API..."
until ollama list >/dev/null 2>&1; do
  sleep 2
done

# Pull embedding model (no-op if already cached in volume)
echo "Pulling nomic-embed-text..."
ollama pull nomic-embed-text

echo "Ollama ready with nomic-embed-text."
wait $SERVE_PID
```

This script:
- Starts `ollama serve` in background
- Polls until the API is responsive via `ollama list`
- Pulls nomic-embed-text (274 MB on first run, instant if cached in named volume)
- Waits on the serve process so the container stays alive

Make the file executable: `chmod +x ollama-entrypoint.sh`



test -f ollama-entrypoint.sh && test -x ollama-entrypoint.sh && grep -q "ollama pull nomic-embed-text" ollama-entrypoint.sh && grep -q "ollama serve" ollama-entrypoint.sh && grep -q "wait \$SERVE_PID" ollama-entrypoint.sh && echo "PASS" || echo "FAIL"


- ollama-entrypoint.sh exists in repo root
- ollama-entrypoint.sh is executable (chmod +x)
- ollama-entrypoint.sh contains #!/bin/sh
- ollama-entrypoint.sh contains ollama serve &
- ollama-entrypoint.sh contains ollama pull nomic-embed-text
- ollama-entrypoint.sh contains wait $SERVE_PID

ollama-entrypoint.sh exists, is executable, starts ollama serve, auto-pulls nomic-embed-text, waits on serve process


Task 2: Create docker-compose.yml with app, db, and ollama services
docker-compose.yml

- .planning/phases/01-docker-compose-stack/01-RESEARCH.md (Code Examples section: docker-compose.yml structure, lines 266-318)
- .env.example (container hostnames and POSTGRES_* vars from Plan 01)
- schema.sql (mounted into db container at /docker-entrypoint-initdb.d/)
- Dockerfile (build context for app service from Plan 01)
- ollama-entrypoint.sh (entrypoint for ollama service from Task 1)


Create docker-compose.yml in the repo root with the following exact content:

```yaml
services:
  db:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-rag}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rag}
      POSTGRES_DB: ${POSTGRES_DB:-supportdesk}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-rag} -d ${POSTGRES_DB:-supportdesk}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 10s
    restart: unless-stopped

  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollama_data:/root/.ollama
      - ./ollama-entrypoint.sh:/ollama-entrypoint.sh:ro
    entrypoint: ["/bin/sh", "/ollama-entrypoint.sh"]
    healthcheck:
      test: ["CMD-SHELL", "ollama list | grep -q nomic-embed-text"]
      interval: 10s
      timeout: 10s
      retries: 30
      start_period: 60s
    restart: unless-stopped

  app:
    build: .
    ports:
      - "8080:8080"
    env_file: .env
    volumes:
      - ./logs:/app/logs
    depends_on:
      db:
        condition: service_healthy
      ollama:
        condition: service_healthy
    restart: unless-stopped

volumes:
  pgdata:
  ollama_data:
```

Key design decisions:
- NO `version:` field (Docker Compose v2 ignores it -- per RESEARCH.md "Deprecated/outdated")
- db: pgvector/pgvector:pg17 (NOT ankane/pgvector). Healthcheck uses pg_isready. schema.sql mounted read-only at /docker-entrypoint-initdb.d/01-schema.sql for auto-init on first run.
- ollama: ollama-entrypoint.sh bind-mounted read-only. Healthcheck verifies nomic-embed-text is loaded (not just server up). start_period: 60s and retries: 30 allow for first-time model pull (274 MB).
- app: builds from Dockerfile in repo root. Port 8080:8080. Reads .env file. logs/ bind-mounted for ingest. depends_on db AND ollama with service_healthy condition.
- Named volumes: pgdata (survives restarts = SVC-04), ollama_data (caches model = avoids re-pull)
- restart: unless-stopped on all services for resilience



test -f docker-compose.yml && grep -q "pgvector/pgvector:pg17" docker-compose.yml && grep -q "ollama/ollama:latest" docker-compose.yml && grep -q "service_healthy" docker-compose.yml && grep -q "pgdata:" docker-compose.yml && grep -q "ollama_data:" docker-compose.yml && grep -q "schema.sql:/docker-entrypoint-initdb.d" docker-compose.yml && grep -q "8080:8080" docker-compose.yml && grep -q "env_file" docker-compose.yml && echo "PASS" || echo "FAIL"


- docker-compose.yml exists in repo root
- docker-compose.yml does NOT contain version:
- db service uses image pgvector/pgvector:pg17
- db service has healthcheck with pg_isready
- db service mounts ./schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
- db service has volume pgdata:/var/lib/postgresql/data
- ollama service uses image ollama/ollama:latest
- ollama service has entrypoint ["/bin/sh", "/ollama-entrypoint.sh"]
- ollama service has healthcheck with ollama list | grep -q nomic-embed-text
- ollama service has volume ollama_data:/root/.ollama
- ollama service mounts ./ollama-entrypoint.sh:/ollama-entrypoint.sh:ro
- app service has build: .
- app service has ports: - "8080:8080"
- app service has env_file: .env
- app service has depends_on with condition: service_healthy for both db and ollama
- app service has volume ./logs:/app/logs
- Top-level volumes section declares pgdata: and ollama_data:

docker-compose.yml defines three services (db, ollama, app) with healthchecks, persistent named volumes, and proper startup ordering


Task 3: Create test log fixture and smoke test script
logs/test.log, smoke_test.sh

- .planning/phases/01-docker-compose-stack/01-VALIDATION.md (Wave 0 Requirements: logs/test.log, smoke_test.sh)
- .planning/phases/01-docker-compose-stack/01-RESEARCH.md (Validation Architecture section, lines 431-463)
- api.py (POST /ingest accepts {"filename": "logs/test.log"}, POST /query accepts {"question": "..."}, GET /health)


1. Create directory logs/ if it does not exist.

2. Create `logs/test.log` with realistic sample log content (at least 25 lines so it produces 2+ chunks at CHUNK_LINES=20):

```
2026-03-22 08:01:12 INFO  mailserver01 postfix/smtpd[12345]: connect from relay.example.com[10.0.1.5]
2026-03-22 08:01:13 INFO  mailserver01 postfix/smtpd[12345]: NOQUEUE: reject: RCPT from relay.example.com[10.0.1.5]: 550 5.1.1 <unknown@example.com>: Recipient address rejected
2026-03-22 08:01:14 INFO  mailserver01 postfix/smtpd[12345]: disconnect from relay.example.com[10.0.1.5]
2026-03-22 08:05:22 WARN  webproxy01 squid[8821]: WARNING: DNS lookup for internal.corp failed
2026-03-22 08:05:23 ERROR webproxy01 squid[8821]: comm_open: socket failure: (97) Address family not supported
2026-03-22 08:10:00 INFO  backup01 rsync[4412]: sending incremental file list
2026-03-22 08:10:05 INFO  backup01 rsync[4412]: sent 1,234,567 bytes  received 890 bytes
2026-03-22 08:10:06 INFO  backup01 rsync[4412]: total size is 45,678,901  speedup is 36.99
2026-03-22 08:15:30 ERROR firewall01 iptables: INPUT DROP: SRC=192.168.1.100 DST=10.0.0.1 PROTO=TCP DPT=22
2026-03-22 08:15:31 ERROR firewall01 iptables: INPUT DROP: SRC=192.168.1.100 DST=10.0.0.1 PROTO=TCP DPT=23
2026-03-22 08:15:32 WARN  firewall01 iptables: rate-limit reached for SSH brute-force rule
2026-03-22 08:20:00 INFO  monitoring nagios[2201]: SERVICE ALERT: mailserver01;SMTP;OK;HARD;3;SMTP OK - 0.023 second response time
2026-03-22 08:20:01 INFO  monitoring nagios[2201]: SERVICE ALERT: webproxy01;HTTP;WARNING;SOFT;1;HTTP WARNING: HTTP/1.1 503 Service Unavailable
2026-03-22 08:25:10 INFO  appserver01 java[9901]: Deployment of webapp.war completed in 12.3s
2026-03-22 08:25:11 WARN  appserver01 java[9901]: Memory usage at 78% of max heap (3.1GB/4GB)
2026-03-22 08:25:12 INFO  appserver01 java[9901]: Active sessions: 142, Peak today: 312
2026-03-22 08:30:00 ERROR database01 mysql[3306]: [ERROR] InnoDB: Unable to lock ./ibdata1 error: 11
2026-03-22 08:30:01 ERROR database01 mysql[3306]: [ERROR] InnoDB: Check that you do not already have another mysqld process using the same InnoDB data or log files
2026-03-22 08:30:02 INFO  database01 mysql[3306]: [Note] InnoDB: Retrying lock on ./ibdata1
2026-03-22 08:30:05 INFO  database01 mysql[3306]: [Note] InnoDB: Lock acquired successfully
2026-03-22 08:35:00 INFO  loadbalancer haproxy[1100]: Server backend/web1 is UP, reason: Layer7 check passed
2026-03-22 08:35:01 WARN  loadbalancer haproxy[1100]: Server backend/web2 is DOWN, reason: Layer4 timeout
2026-03-22 08:35:02 INFO  loadbalancer haproxy[1100]: backend has 2/3 active servers
2026-03-22 08:40:00 INFO  dns01 named[5500]: zone example.com/IN: refresh: retry limit for master 10.0.0.53 exceeded
2026-03-22 08:40:01 WARN  dns01 named[5500]: zone example.com/IN: Transfer started.
```

3. Create `smoke_test.sh` in the repo root with the following content:

```bash
#!/usr/bin/env bash
# smoke_test.sh -- Phase 1 smoke tests for RAG Support Desk Docker stack
# Usage: bash smoke_test.sh
set -euo pipefail

PASS=0
FAIL=0
BASE="http://localhost:8080"

check() {
  local name="$1"; shift
  if "$@" >/dev/null 2>&1; then
    echo "[PASS] $name"
    PASS=$((PASS + 1))
  else
    echo "[FAIL] $name"
    FAIL=$((FAIL + 1))
  fi
}

echo "=== RAG Support Desk Smoke Tests ==="
echo ""

# SVC-06: Health endpoint responds
check "SVC-06: Health endpoint" curl -sf "$BASE/health"

# SVC-06: Health returns ok status
check "SVC-06: Health returns ok" bash -c "curl -sf $BASE/health | grep -q ok"

# OPS-03: App logs contain uvicorn output
check "OPS-03: App logs visible" docker compose logs app --no-color 2>&1 | grep -q "uvicorn"

# SVC-03: Ollama has nomic-embed-text
check "SVC-03: Ollama model loaded" docker compose exec -T ollama ollama list 2>&1 | grep -q "nomic-embed-text"

# SVC-04: Data survives restart
check "SVC-04: DB survives restart" bash -c "docker compose restart db && sleep 5 && curl -sf $BASE/health"

# ING-04: Ingest endpoint accepts test log
check "ING-04: Ingest test.log" curl -sf -X POST "$BASE/ingest" \
  -H "Content-Type: application/json" \
  -d '{"filename":"logs/test.log"}'

# QRY: Query endpoint responds (requires ANTHROPIC_API_KEY)
check "QRY: Query responds" curl -sf -X POST "$BASE/query" \
  -H "Content-Type: application/json" \
  -d '{"question":"Was ist mit dem Mailserver passiert?"}'

echo ""
echo "=== Results: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
```

Make smoke_test.sh executable: `chmod +x smoke_test.sh`



test -f logs/test.log && wc -l < logs/test.log | xargs test 20 -le && test -f smoke_test.sh && test -x smoke_test.sh && grep -q "SVC-06" smoke_test.sh && grep -q "OPS-03" smoke_test.sh && grep -q "SVC-03" smoke_test.sh && grep -q "ING-04" smoke_test.sh && echo "PASS" || echo "FAIL"


- logs/test.log exists with at least 25 lines of realistic log content
- logs/test.log contains entries from multiple systems (mailserver, webproxy, firewall, database, etc.)
- smoke_test.sh exists in repo root and is executable
- smoke_test.sh tests health endpoint (SVC-06)
- smoke_test.sh tests docker compose logs (OPS-03)
- smoke_test.sh tests ollama model presence (SVC-03)
- smoke_test.sh tests DB restart persistence (SVC-04)
- smoke_test.sh tests ingest with logs/test.log (ING-04)
- smoke_test.sh exits 0 on all pass, 1 on any failure

logs/test.log has 25+ lines of sample logs, smoke_test.sh covers SVC-03/SVC-04/SVC-06/ING-04/OPS-03 with pass/fail reporting


- docker compose config validates the compose file syntax
- ls -la ollama-entrypoint.sh shows executable permissions
- wc -l logs/test.log shows 25+ lines
- bash -n smoke_test.sh validates shell syntax


- docker-compose.yml defines three services with healthchecks and named volumes
- ollama-entrypoint.sh auto-pulls nomic-embed-text
- logs/test.log provides realistic test data for ingest
- smoke_test.sh validates all Phase 1 requirements

After completion, create `.planning/phases/01-docker-compose-stack/01-02-SUMMARY.md`