--- phase: 01-infrastructure-foundation plan: "01" subsystem: infrastructure tags: - docker-compose - postgresql - minio - redis - celery - pydantic-settings dependency_graph: requires: [] provides: - five-service compose stack (postgres, minio, redis, backend, celery-worker) - two-DSN postgresql user provisioning - pydantic-settings config class - phase-1 dependency manifest affects: - all subsequent plans (infrastructure foundation) tech_stack: added: - "sqlalchemy[asyncio]>=2.0.49" - "psycopg[binary]>=3.3.4" - "alembic>=1.18.4" - "minio>=7.2.20" - "celery[redis]>=5.6.3" - "redis>=7.4.0" - "aiosqlite>=0.20.0" removed: - "filelock>=3.14" patterns: - pydantic-settings v2 SettingsConfigDict (not deprecated class Config form) - docker compose service_healthy depends_on conditions - two-DSN postgresql strategy (app user + migrate user) - mc ready local healthcheck for MinIO (curl removed from image) - redis-cli -a $REDIS_PASSWORD ping healthcheck (NOAUTH prevention) key_files: created: - docker-compose.yml - docker/postgres/initdb.d/01-init-users.sql - .gitignore modified: - .env.example - backend/config.py - backend/requirements.txt decisions: - "Two-DSN PostgreSQL strategy: DATABASE_URL (docuvault_app, DML only) + DATABASE_MIGRATE_URL (docuvault_migrate, DDL only)" - "MinIO healthcheck uses mc ready local — curl removed from image since Oct 2023" - "Redis healthcheck passes -a $REDIS_PASSWORD — bare ping returns NOAUTH with requirepass" - "Legacy flat-file constants (DATA_DIR, UPLOADS_DIR, DEFAULT_SETTINGS etc.) kept in config.py until Plan 05" - "pydantic-settings v2 SettingsConfigDict API used (not deprecated class Config)" metrics: duration: "~6 minutes" completed_date: "2026-05-22" tasks_completed: 3 tasks_total: 3 files_created: 3 files_modified: 3 --- # Phase 1 Plan 1: Docker Compose + Config Foundation Summary **One-liner:** Five-service Compose stack (postgres:17-alpine + minio + redis:7-alpine + celery-worker + backend) with health-checked depends_on, two-user PostgreSQL init script, and Pydantic Settings class reading all Phase 1 env vars. ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 | Replace docker-compose.yml with five-service stack | `983ecd8` | docker-compose.yml, docker/postgres/initdb.d/01-init-users.sql, .gitignore | | 2 | Extend .env.example with all Phase 1 variables | `beb55ca` | .env.example | | 3 | Replace backend/config.py with Pydantic Settings + extend requirements.txt | `6c507d5` | backend/config.py, backend/requirements.txt | ## What Was Built ### docker-compose.yml Rewrote from a 2-service file (backend + frontend) to a full 6-service Phase 1 stack: - **postgres** (postgres:17-alpine): health-checked with `pg_isready -U postgres -d docuvault`; mounts `docker/postgres/initdb.d` as `/docker-entrypoint-initdb.d:ro` - **minio** (minio/minio:latest): health-checked with `mc ready local` (curl removed from image); exposes ports 9000 and 9001 - **redis** (redis:7-alpine): runs `redis-server --requirepass ${REDIS_PASSWORD}`; health-checked with `redis-cli -a ${REDIS_PASSWORD} ping` to avoid NOAUTH errors - **backend**: depends on all three infra services with `condition: service_healthy`; removed `./backend/data:/app/data` volume per D-04 - **celery-worker**: same build as backend; command `celery -A celery_app worker --loglevel=info -Q documents` per D-10; has `DATABASE_URL` but NOT `DATABASE_MIGRATE_URL` (workers need no DDL) - **frontend**: unchanged from original - Top-level `volumes:` block with `postgres_data:` and `minio_data:` named volumes ### docker/postgres/initdb.d/01-init-users.sql Provisions both PostgreSQL users on first container start: - `docuvault_migrate` with `GRANT ALL PRIVILEGES ON DATABASE docuvault` (DDL — Alembic only) - `docuvault_app` with `GRANT CONNECT ON DATABASE docuvault` (DML — FastAPI + Celery) - Comment notes that table-level grants are issued in the Alembic migration (Plan 03) ### .env.example Extended from 6 lines to a fully documented 4-section env file with 14 named variables grouped by service. All passwords use `changeme_*` placeholders consistent between the env file and the SQL init script. ### backend/config.py Added `Settings(BaseSettings)` class with `SettingsConfigDict` (pydantic-settings v2 API). Instantiates `settings = Settings()` at module level. Preserved all legacy flat-file constants verbatim (`DATA_DIR`, `UPLOADS_DIR`, `METADATA_DIR`, `TOPICS_FILE`, `SETTINGS_FILE`, `DEFAULT_SYSTEM_PROMPT`, `DEFAULT_SETTINGS`, `ensure_data_dirs()`). ### backend/requirements.txt Removed `filelock>=3.14`. Added 7 new dependencies: `sqlalchemy[asyncio]>=2.0.49`, `psycopg[binary]>=3.3.4`, `alembic>=1.18.4`, `minio>=7.2.20`, `celery[redis]>=5.6.3`, `redis>=7.4.0`, `aiosqlite>=0.20.0`. Bumped `pytest-asyncio` to `>=1.3.0`. ## Verification Results - `docker compose --env-file .env.example config -q` exits 0 (no parse errors) - `from config import settings; settings.minio_bucket` returns `"docuvault"` - `settings.database_url` starts with `postgresql+psycopg://` - `grep -c "filelock" backend/requirements.txt` returns 0 - All 14 env vars present in `.env.example` - All 6 service keys present in `docker-compose.yml` - SQL init script creates both `docuvault_app` and `docuvault_migrate` users ## Deviations from Plan None — plan executed exactly as written. The legacy flat-file constants were preserved verbatim as specified in the plan's `` section. The `SettingsConfigDict` approach (pydantic-settings v2 API) was used as specified. The `celery-worker` service omits `DATABASE_MIGRATE_URL` as specified (workers need no DDL access). ## Known Stubs None — this plan is infrastructure and configuration only. No UI data flows or component wiring involved. ## Threat Flags None — all threat mitigations from the plan's threat model are implemented: - T-01-01-01 (PostgreSQL EoP): Two-DSN pattern implemented (app user DML-only, migrate user DDL-only) - T-01-01-02 (MinIO root creds): MINIO_ROOT_USER/PASSWORD separate from MINIO_ACCESS_KEY/SECRET_KEY in both compose and env.example - T-01-01-03 (Redis unauthenticated): `--requirepass ${REDIS_PASSWORD}` + authenticated healthcheck - T-01-01-04 (Secret leakage): `.env` added to `.gitignore`; only `.env.example` with `changeme_*` values committed - T-01-01-05 (Race condition startup): `depends_on: condition: service_healthy` on all infra dependencies ## Self-Check: PASSED - [x] docker-compose.yml exists and contains 6 services - [x] docker/postgres/initdb.d/01-init-users.sql exists - [x] .gitignore contains `.env` - [x] .env.example has 14 variables - [x] backend/config.py has Settings class and settings instance - [x] backend/requirements.txt has no filelock, has all 7 new packages - [x] Commits 983ecd8, beb55ca, 6c507d5 confirmed in git log