diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 000000000..cae8800a6 --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,61 @@ +# syntax=docker/dockerfile:1 +# +# apps/api — unified Hono/Bun API server +# +# Multi-stage build that runs from the monorepo root so the workspace +# packages (@mana/shared-hono, @mana/shared-storage, @mana/media-client, +# @mana/shared-logger) resolve via pnpm before being copied into a +# minimal runtime image. +# +# Build context MUST be the monorepo root, not apps/api/. The compose +# service uses `context: .` for this reason. + +FROM oven/bun:1 AS builder + +WORKDIR /app + +# Copy the workspace manifest first so the dependency graph is known +# before we add source. This caches the install layer for incremental +# rebuilds when only source changes. +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/api/package.json ./apps/api/package.json + +# Workspace packages that apps/api depends on, plus their transitive +# workspace deps. Listed explicitly so the install layer doesn't pull +# in the entire monorepo. +COPY packages/shared-hono ./packages/shared-hono +COPY packages/shared-logger ./packages/shared-logger +COPY packages/shared-storage ./packages/shared-storage +# @mana/media-client lives under services/mana-media (sub-package). +COPY services/mana-media/packages/client ./services/mana-media/packages/client + +# Install pnpm and resolve the dependency graph for apps/api. +RUN npm install -g pnpm@9.15.0 +RUN pnpm install --filter @mana/api... --no-frozen-lockfile --ignore-scripts + +# Copy the api source and tsconfig last so source-only changes don't +# bust the install cache. +COPY apps/api/src ./apps/api/src +COPY apps/api/tsconfig.json ./apps/api/tsconfig.json + + +# ─── Runtime stage ───────────────────────────────────────────── +# +# Bun can run TypeScript directly without a compile step, so the +# runtime image just needs the workspace tree the builder produced. +# We copy /app wholesale rather than try to slice node_modules — the +# pnpm symlink farm is fragile and easy to break with selective copies. + +FROM oven/bun:1 AS production + +WORKDIR /app +COPY --from=builder /app /app + +WORKDIR /app/apps/api + +EXPOSE 3060 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ + CMD bun -e "fetch('http://localhost:3060/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" + +CMD ["bun", "run", "src/index.ts"] diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index 3e3f20dcb..ebc5ab717 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -746,6 +746,8 @@ services: depends_on: mana-auth: condition: service_healthy + mana-api: + condition: service_healthy environment: NODE_ENV: production PORT: 5000 @@ -753,6 +755,13 @@ services: PUBLIC_MANA_AUTH_URL_CLIENT: https://auth.mana.how PUBLIC_SYNC_SERVER_URL: http://mana-sync:3010 PUBLIC_SYNC_SERVER_URL_CLIENT: https://sync.mana.how + # Unified Hono/Bun API server (apps/api) — hosts all 16 product + # compute modules (calendar, todo, picture, … who) on port 3060. + # Browser calls go through https://api.mana.how (cloudflared + # tunnel route to mana-api:3060). SSR calls inside the docker + # network use the internal hostname. + PUBLIC_MANA_API_URL: http://mana-api:3060 + PUBLIC_MANA_API_URL_CLIENT: https://api.mana.how # Per-app HTTP backend URLs (todo-api, calendar-api, contacts-api, # chat-api, storage-api, cards-api, music-api, nutriphi-api, # picture-api, presi-api, zitare-api, clock-api, context-api) and @@ -1441,6 +1450,61 @@ services: redis: condition: service_healthy + # ============================================ + # Unified API Server + # ============================================ + # apps/api — Hono/Bun process that hosts all 16 product compute + # modules (calendar, todo, chat, picture, planta, nutriphi, news, + # traces, moodlit, presi, music, contacts, storage, context, guides, + # research, who) on a single port. Replaces ~17 per-product backend + # containers from the pre-consolidation era; the unified Mana web + # app's compute calls all flow through here. + + mana-api: + build: + context: . + dockerfile: apps/api/Dockerfile + image: mana-api:local + container_name: mana-api + restart: always + mem_limit: 384m + depends_on: + postgres: + condition: service_healthy + mana-auth: + condition: service_healthy + environment: + TZ: Europe/Berlin + NODE_ENV: production + PORT: 3060 + # Auth (JWT validation via JWKS) + MANA_AUTH_URL: http://mana-auth:3001 + JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY:-} + # Compute services apps/api orchestrates + MANA_LLM_URL: http://mana-llm:3025 + MANA_SEARCH_URL: http://mana-search:3012 + MANA_CREDITS_URL: http://mana-credits:3002 + MANA_MEDIA_URL: http://mana-media:3011 + MANA_SERVICE_KEY: ${MANA_SERVICE_KEY} + APP_ID: mana-api + # Database (used by modules that have server-side state — research, + # presi share-links, traces guides). Same Postgres + schema split + # as the rest of the platform. + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_platform + # CORS — only the unified mana.how origin needs access today. + # The arcade + manavoxel game frontends don't call apps/api. + CORS_ORIGINS: https://mana.how + # Structured-logger format + LOGGER_FORMAT: json + ports: + - "3060:3060" + healthcheck: + test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3060/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] + interval: 60s + timeout: 5s + retries: 3 + start_period: 30s + # ============================================ # Games # ============================================