--- phase: 01-foundation-portal-shell plan: 01 subsystem: infra tags: [docker, docker-compose, traefik, nestjs, nextjs, prisma, postgresql, pnpm, turborepo, biome, typescript, tailwindcss, monorepo] # Dependency graph requires: [] provides: - Docker Compose stack with 4 services (traefik, web, api, db) - pnpm monorepo with Turborepo build orchestration - NestJS API with /health endpoint - Next.js frontend with standalone Docker output - PostgreSQL database on internal network - Three-network segmentation (frontend-net, backend-net, data-net) - Prisma schema with Tenant model foundation - @tessera/shared package with APP_NAME and HealthResponse type - TypeScript base config with strict mode - Biome linter/formatter config affects: [01-02-portal-shell, 01-03-portal-shell, phase-2-auth, phase-3-modules] # Tech tracking tech-stack: added: [pnpm@9.15.0, turbo@2.9.18, biome@2.5.0, typescript@5.9.3, nestjs@11, nextjs@15.5.19, prisma@6, postgresql@16, traefik@2.11, tailwindcss@4, react@19] patterns: [monorepo-workspace, multi-stage-docker-build, network-segmentation, health-check-endpoint] key-files: created: - package.json - pnpm-workspace.yaml - turbo.json - tsconfig.base.json - biome.json - docker-compose.yml - docker-compose.dev.yml - apps/api/src/main.ts - apps/api/src/health/health.controller.ts - apps/api/prisma/schema.prisma - apps/api/Dockerfile - apps/web/src/app/layout.tsx - apps/web/src/app/page.tsx - apps/web/Dockerfile - packages/shared/src/index.ts modified: - .gitignore - .dockerignore key-decisions: - "Used Traefik v2.11 instead of v3.4 due to Docker API version incompatibility on host" - "API Dockerfile copies full monorepo node_modules structure to resolve pnpm workspace symlinks" - "Next.js standalone output in monorepo runs from apps/web/server.js (not root server.js)" - "Traefik placed on both frontend-net and backend-net for routing to both web and api services" patterns-established: - "Monorepo structure: apps/* for deployables, packages/* for shared code" - "Multi-stage Docker builds with monorepo root as build context" - "Network segmentation: frontend-net (public), backend-net (internal routing), data-net (internal: true for DB)" - "Health check pattern: /health endpoint for container orchestration" - "Workspace dependencies via workspace:* protocol" requirements-completed: [INFRA-01, INFRA-02, INFRA-03] # Metrics duration: 16min completed: 2026-06-18 --- # Phase 1 Plan 01: Walking Skeleton Summary **Docker Compose stack with pnpm monorepo, NestJS API health endpoint, Next.js frontend, PostgreSQL on internal network, Traefik reverse proxy with three-network segmentation** ## Performance - **Duration:** 16 min - **Started:** 2026-06-18T08:01:17Z - **Completed:** 2026-06-18T08:17:51Z - **Tasks:** 3 - **Files modified:** 25 ## Accomplishments - Full Docker Compose stack starts with `docker compose up` serving web at localhost:80 and API at localhost:80/api/health - pnpm monorepo with Turborepo build orchestration, all packages type-check clean - Three-network Docker segmentation: PostgreSQL isolated on internal data-net, web cannot reach DB directly - Multi-stage Docker builds for both NestJS and Next.js with non-root users ## Task Commits Each task was committed atomically: 1. **Task 1: Monorepo scaffold with root configs and shared package** - `b75d7c3` (feat) 2. **Task 2: NestJS API with health endpoint and Prisma schema** - `04c78b4` (feat) 3. **Task 3: Next.js app + Docker Compose stack with network segmentation** - `2c12878` (feat) 4. **Chore: Add tsbuildinfo to gitignore** - `dbe2d50` (chore) ## Files Created/Modified - `package.json` - Root monorepo config with pnpm workspace and Turborepo scripts - `pnpm-workspace.yaml` - Workspace definition for apps/* and packages/* - `turbo.json` - Build orchestration with task dependency graph - `tsconfig.base.json` - Shared TypeScript config with strict mode - `biome.json` - Linter and formatter configuration - `.gitignore` - Ignores node_modules, .next, dist, .turbo, .env, tsbuildinfo - `.env.example` - Template for environment variables - `.dockerignore` - Excludes build artifacts from Docker context - `packages/shared/src/index.ts` - APP_NAME constant and HealthResponse interface - `apps/api/src/main.ts` - NestJS bootstrap listening on port 3001 - `apps/api/src/app.module.ts` - Root module with ConfigModule and HealthModule - `apps/api/src/health/health.controller.ts` - GET /health returning {status, timestamp} - `apps/api/src/health/health.module.ts` - Health feature module - `apps/api/prisma/schema.prisma` - PostgreSQL datasource with Tenant model - `apps/api/Dockerfile` - Multi-stage build, non-root nestjs user - `apps/web/src/app/layout.tsx` - Root layout with de locale - `apps/web/src/app/page.tsx` - Placeholder page with "Tessera" heading - `apps/web/src/app/globals.css` - Tailwind CSS v4 import - `apps/web/next.config.ts` - Standalone output mode - `apps/web/postcss.config.mjs` - @tailwindcss/postcss plugin - `apps/web/Dockerfile` - Multi-stage build, non-root nextjs user - `docker-compose.yml` - 4 services, 3 networks, Traefik routing - `docker-compose.dev.yml` - Volume mounts for hot reload ## Decisions Made - **Traefik v2.11 instead of v3.4:** Traefik v3.3+ ships with Docker API client v1.24 which is below the minimum v1.40 required by Docker Engine 29.x on this host. Downgraded to v2.11 which works correctly. Future upgrade possible when Traefik v3 fixes API negotiation. - **Traefik on backend-net:** Added Traefik to backend-net (in addition to frontend-net) so it can route /api/* requests to the API container. This is necessary since Traefik discovers and routes to containers only on shared networks. - **API Dockerfile with full node_modules:** pnpm monorepo uses symlinks in per-package node_modules pointing to root. The runner stage copies both root and per-package node_modules to preserve the link structure. - **Next.js standalone monorepo path:** In a monorepo, Next.js standalone output places server.js at apps/web/server.js (not root). CMD adjusted accordingly. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Fixed TypeScript moduleResolution conflict in API tsconfig** - **Found during:** Task 2 (NestJS API) - **Issue:** Base tsconfig has moduleResolution "bundler" which is incompatible with module "commonjs" in API tsconfig - **Fix:** Added moduleResolution "node" override in apps/api/tsconfig.json - **Files modified:** apps/api/tsconfig.json - **Verification:** pnpm turbo type-check --filter=@tessera/api passes **2. [Rule 3 - Blocking] Added @tessera/shared workspace dependency to API** - **Found during:** Task 2 (NestJS API) - **Issue:** Health controller imports HealthResponse from @tessera/shared but no workspace dependency declared - **Fix:** Added "@tessera/shared": "workspace:*" to apps/api/package.json dependencies - **Files modified:** apps/api/package.json - **Verification:** Type-check passes, import resolves correctly **3. [Rule 1 - Bug] Fixed API Dockerfile for pnpm monorepo node_modules** - **Found during:** Task 3 (Docker Compose) - **Issue:** API container crashed with "Cannot find module @nestjs/core" - runner stage copied empty per-package node_modules - **Fix:** Restructured Dockerfile to copy root node_modules and preserve pnpm workspace symlink structure - **Files modified:** apps/api/Dockerfile - **Verification:** Container starts and /health endpoint responds **4. [Rule 1 - Bug] Fixed Next.js standalone path for monorepo** - **Found during:** Task 3 (Docker Compose) - **Issue:** Web container crashed with "Cannot find module /app/server.js" - standalone output in monorepo places server at apps/web/server.js - **Fix:** Updated Dockerfile COPY paths and CMD to use apps/web/ prefix - **Files modified:** apps/web/Dockerfile - **Verification:** Container starts and serves pages **5. [Rule 1 - Bug] Fixed Next.js listening on container hostname instead of 0.0.0.0** - **Found during:** Task 3 (Docker Compose) - **Issue:** Next.js standalone server bound to container hostname, not accessible from other containers - **Fix:** Added HOSTNAME: "0.0.0.0" environment variable in docker-compose.yml web service - **Files modified:** docker-compose.yml - **Verification:** curl http://localhost:80 returns Tessera page via Traefik **6. [Rule 1 - Bug] Fixed Traefik Docker API version incompatibility** - **Found during:** Task 3 (Docker Compose) - **Issue:** Traefik v3.4 and v3.3 use Docker API v1.24 which Docker Engine 29.x rejects (minimum 1.40) - **Fix:** Downgraded to Traefik v2.11 which negotiates API version correctly - **Files modified:** docker-compose.yml - **Verification:** Traefik starts, discovers services, routes requests correctly --- **Total deviations:** 6 auto-fixed (4 bugs, 1 blocking, 1 blocking) **Impact on plan:** All fixes necessary for the stack to function. No scope creep. The Docker-related issues are common monorepo + standalone pitfalls documented in the research as Pitfall 4. ## Issues Encountered - Traefik v3.x Docker provider API version negotiation is broken with Docker Engine 29.x -- this is a known upstream issue. Resolved by using Traefik v2.11 which correctly handles version negotiation. ## User Setup Required None - no external service configuration required. The stack runs with default development credentials. ## Known Stubs - `apps/web/src/app/page.tsx` - Placeholder page with "Tessera" heading and "Portal wird geladen..." text. Intentional skeleton -- Plan 01-02 replaces with full portal shell. ## Next Phase Readiness - Docker Compose stack is fully operational for Plan 01-02 (Portal Shell with i18n, theming, layout) - All build tooling (Turborepo, Biome, TypeScript) is configured and working - Prisma schema ready for migrations when database is needed - Both apps containerized with multi-stage builds ## Self-Check: PASSED All 15 key files verified present. All 4 commit hashes verified in git log. --- *Phase: 01-foundation-portal-shell* *Completed: 2026-06-18*