From 4e232e8ef6dc600353c216cd6278acaaa175a313 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 25 Aug 2026 00:48:41 +0200 Subject: [PATCH] Fix master-api Docker runtime: flat deps, explicit env, longer health start period. Co-authored-by: Cursor --- apps/master-api/package.json | 2 +- deploy/docker/Dockerfile.master-api | 56 +++++++++++++++--------- deploy/docker/docker-compose.dokploy.yml | 45 ++++++++++--------- 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/apps/master-api/package.json b/apps/master-api/package.json index cb5ccc9..40160f4 100644 --- a/apps/master-api/package.json +++ b/apps/master-api/package.json @@ -25,13 +25,13 @@ "fastify": "^5.2.1", "ioredis": "^5.4.2", "nanoid": "^5.0.9", + "prisma": "^6.3.1", "stremio-addon-sdk": "^1.6.10", "zod": "^3.24.1" }, "devDependencies": { "@types/node": "^22.10.7", "@types/ws": "^8.5.14", - "prisma": "^6.3.1", "tsx": "^4.19.2", "typescript": "^5.7.3" } diff --git a/deploy/docker/Dockerfile.master-api b/deploy/docker/Dockerfile.master-api index 262a961..de9af97 100644 --- a/deploy/docker/Dockerfile.master-api +++ b/deploy/docker/Dockerfile.master-api @@ -2,33 +2,49 @@ FROM node:20-alpine AS base RUN corepack enable && corepack prepare pnpm@9.15.4 --activate WORKDIR /app -FROM base AS deps -COPY package.json pnpm-workspace.yaml ./ -COPY apps/master-api/package.json ./apps/master-api/ -COPY packages/shared-types/package.json ./packages/shared-types/ -COPY packages/protocol/package.json ./packages/protocol/ -RUN pnpm install --no-frozen-lockfile - FROM base AS builder -COPY --from=deps /app/node_modules ./node_modules -COPY --from=deps /app/apps ./apps -COPY --from=deps /app/packages ./packages COPY package.json pnpm-workspace.yaml ./ -COPY packages ./packages COPY apps/master-api ./apps/master-api -RUN pnpm --filter @media-cluster/shared-types build \ - && pnpm --filter @media-cluster/protocol build +COPY packages ./packages +RUN pnpm install --no-frozen-lockfile +RUN pnpm --filter @media-cluster/shared-types build +RUN pnpm --filter @media-cluster/protocol build WORKDIR /app/apps/master-api -RUN pnpm exec prisma generate && pnpm build +RUN pnpm exec prisma generate +RUN pnpm build + +# Assemble a flat runtime directory (avoids broken pnpm symlinks in final image) +WORKDIR /app +RUN mkdir -p /out/node_modules/@media-cluster \ + && cp -R apps/master-api/dist /out/dist \ + && cp -R apps/master-api/prisma /out/prisma \ + && cp apps/master-api/package.json /out/package.json \ + && cp -R packages/shared-types /out/node_modules/@media-cluster/shared-types \ + && cp -R packages/protocol /out/node_modules/@media-cluster/protocol FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production -RUN apk add --no-cache openssl wget -COPY --from=builder /app/apps/master-api/dist ./dist -COPY --from=builder /app/apps/master-api/package.json ./package.json -COPY --from=builder /app/apps/master-api/prisma ./prisma -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/packages ./packages +RUN apk add --no-cache openssl wget python3 make g++ +COPY --from=builder /out/ ./ + +# Drop workspace: protocol deps; keep prisma CLI for migrate +RUN node -e "\ +const fs=require('fs');\ +const p=JSON.parse(fs.readFileSync('package.json','utf8'));\ +delete p.dependencies['@media-cluster/protocol'];\ +delete p.dependencies['@media-cluster/shared-types'];\ +p.dependencies.prisma = (p.devDependencies && p.devDependencies.prisma) || '6.3.1';\ +delete p.devDependencies;\ +delete p.scripts;\ +fs.writeFileSync('package.json', JSON.stringify(p, null, 2));" + +RUN npm install --omit=dev \ + && npx prisma generate \ + && apk del python3 make g++ + EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=5 \ + CMD wget -qO- http://127.0.0.1:3000/health || exit 1 + CMD ["sh", "-c", "npx prisma migrate deploy && node dist/app.js"] diff --git a/deploy/docker/docker-compose.dokploy.yml b/deploy/docker/docker-compose.dokploy.yml index 06648ec..5b3ebff 100644 --- a/deploy/docker/docker-compose.dokploy.yml +++ b/deploy/docker/docker-compose.dokploy.yml @@ -1,9 +1,8 @@ -# Docker Compose tuned for Dokploy -# Deploy as: Compose application → Git provider → compose path: -# deploy/docker/docker-compose.dokploy.yml +# Docker Compose for Dokploy +# Compose path: deploy/docker/docker-compose.dokploy.yml # -# Set environment variables in the Dokploy UI (Environment tab). -# Dokploy writes them to .env next to this compose file. +# Put secrets in Dokploy Environment tab (NOT env_file). +# Dokploy injects them for ${VAR} substitution. services: postgres: @@ -11,15 +10,16 @@ services: restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-media} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} POSTGRES_DB: ${POSTGRES_DB:-media_cluster} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-media} -d $${POSTGRES_DB:-media_cluster}"] + test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""] interval: 10s timeout: 5s - retries: 5 + retries: 10 + start_period: 10s networks: - media-cluster @@ -32,7 +32,7 @@ services: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s - retries: 5 + retries: 10 networks: - media-cluster @@ -41,28 +41,35 @@ services: context: ../.. dockerfile: deploy/docker/Dockerfile.master-api restart: unless-stopped - env_file: - - .env environment: NODE_ENV: production HOST: 0.0.0.0 - PORT: 3000 + PORT: "3000" DATABASE_URL: postgresql://${POSTGRES_USER:-media}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-media_cluster} REDIS_URL: redis://redis:6379 + SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required} + ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@localhost} + ADMIN_PASSWORD: ${ADMIN_PASSWORD:-changeme123} + PUBLIC_URL: ${PUBLIC_URL:-https://master.vonas.nl} + TMDB_API_KEY: ${TMDB_API_KEY:-} + PLAYBACK_IDLE_TIMEOUT_SECONDS: ${PLAYBACK_IDLE_TIMEOUT_SECONDS:-1200} + PLAYBACK_ABSOLUTE_LIFETIME_SECONDS: ${PLAYBACK_ABSOLUTE_LIFETIME_SECONDS:-43200} + NODE_HEARTBEAT_INTERVAL_SECONDS: ${NODE_HEARTBEAT_INTERVAL_SECONDS:-30} + NODE_OFFLINE_THRESHOLD_SECONDS: ${NODE_OFFLINE_THRESHOLD_SECONDS:-90} + HIDE_UNAVAILABLE_TITLES: ${HIDE_UNAVAILABLE_TITLES:-false} depends_on: postgres: condition: service_healthy redis: condition: service_healthy - # Host ports (avoid Dokploy UI on :3000) ports: - "3100:3000" healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] - interval: 30s + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"] + interval: 15s timeout: 5s - retries: 3 - start_period: 40s + retries: 10 + start_period: 90s networks: media-cluster: aliases: @@ -78,12 +85,10 @@ services: environment: HOSTNAME: 0.0.0.0 PORT: "3000" - # Prefer compose DNS; fallback via published host port MASTER_API_URL: http://master-api:3000 NEXT_PUBLIC_API_URL: http://master-api:3000 depends_on: - master-api: - condition: service_healthy + - master-api ports: - "3101:3000" networks: