diff --git a/.env.production.template b/.env.production.template new file mode 100644 index 0000000..9e6c2b0 --- /dev/null +++ b/.env.production.template @@ -0,0 +1,30 @@ +# claudemesh — production env template +# Copy to .env.production and fill in real values. NEVER commit .env.production. +# Generate secrets with: openssl rand -base64 32 + +# ── Database (managed by Coolify or external) ──────────────────────────────── +DATABASE_URL=postgres://claudemesh:CHANGE_ME@db:5432/claudemesh + +# ── Broker ─────────────────────────────────────────────────────────────────── +BROKER_PORT=7900 +STATUS_TTL_SECONDS=60 +HOOK_FRESH_WINDOW_SECONDS=30 +# Hardening caps (see apps/broker/DEPLOY_SPEC.md) +MAX_CONNECTIONS_PER_MESH=100 +MAX_MESSAGE_BYTES=65536 +HOOK_RATE_LIMIT_PER_MIN=30 + +# ── Auth (BetterAuth) ──────────────────────────────────────────────────────── +BETTER_AUTH_SECRET=CHANGE_ME_openssl_rand_base64_32 +BETTER_AUTH_URL=https://claudemesh.com +BETTER_AUTH_TRUSTED_ORIGINS=https://claudemesh.com,https://dashboard.claudemesh.com,https://ic.claudemesh.com + +# ── OAuth providers ────────────────────────────────────────────────────────── +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# ── Image refs (set by CI/CD after docker push) ────────────────────────────── +BROKER_IMAGE=registry.claudemesh.com/claudemesh/broker:latest +WEB_IMAGE=registry.claudemesh.com/claudemesh/web:latest diff --git a/apps/broker/Dockerfile b/apps/broker/Dockerfile new file mode 100644 index 0000000..0d67c56 --- /dev/null +++ b/apps/broker/Dockerfile @@ -0,0 +1,47 @@ +# claudemesh broker — production Dockerfile +# Bun runtime (executes .ts directly, no build step required). +# Build from repo root: docker build -f apps/broker/Dockerfile -t claudemesh-broker . + +# Stage 1: resolve pnpm workspace + install deps (Bun base + standalone pnpm) +FROM oven/bun:1.2 AS deps +WORKDIR /app + +# Install standalone pnpm binary (no Node needed — pnpm ships as a single ELF) +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \ + curl -fsSL "https://github.com/pnpm/pnpm/releases/download/v10.25.0/pnpm-linuxstatic-x64" -o /usr/local/bin/pnpm && \ + chmod +x /usr/local/bin/pnpm && \ + rm -rf /var/lib/apt/lists/* + +# Copy full workspace (pnpm needs lockfile + all package.jsons to resolve workspace:* and catalog:) +COPY . . + +# Install all workspace deps (broker needs @turbostarter/db + @turbostarter/shared and their transitive deps) +RUN pnpm install --frozen-lockfile --ignore-scripts + +# Stage 2: minimal Bun runtime +FROM oven/bun:1.2-slim AS runtime +WORKDIR /app + +# Git SHA baked in at build-time → surfaced on /health (spec: apps/broker/DEPLOY_SPEC.md) +ARG GIT_SHA=unknown +ENV GIT_SHA=$GIT_SHA + +ENV NODE_ENV=production +ENV BROKER_PORT=7900 + +# Copy workspace root metadata + node_modules + only the packages the broker needs +COPY --from=deps --chown=bun:bun /app/package.json /app/pnpm-workspace.yaml /app/pnpm-lock.yaml /app/.npmrc ./ +COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules +COPY --from=deps --chown=bun:bun /app/apps/broker ./apps/broker +COPY --from=deps --chown=bun:bun /app/packages/db ./packages/db +COPY --from=deps --chown=bun:bun /app/packages/shared ./packages/shared +COPY --from=deps --chown=bun:bun /app/tooling/typescript ./tooling/typescript + +EXPOSE 7900 + +HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \ + CMD bun -e "fetch('http://localhost:7900/health').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" + +# Non-root user (oven/bun image ships with 'bun' uid 1000) +USER bun +CMD ["bun", "apps/broker/src/index.ts"] diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..9e9b832 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,52 @@ +# claudemesh web (Next.js) — production Dockerfile +# Build from repo root: docker build -f apps/web/Dockerfile -t claudemesh-web . + +# Stage 1: builder — install + turbo build (Next.js standalone output) +FROM node:22-slim AS builder +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@10.25.0 --activate + +# pnpm workspace needs full context to resolve workspace:* + catalog: +COPY . . + +RUN pnpm install --frozen-lockfile + +# Build — SKIP_ENV_VALIDATION lets missing runtime vars pass (validated at startup instead) +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV SKIP_ENV_VALIDATION=1 + +# NEXT_PUBLIC_* vars are BAKED at build time in Next standalone — must be passed as build args +ARG NEXT_PUBLIC_URL=https://claudemesh.com +ARG NEXT_PUBLIC_PRODUCT_NAME=claudemesh +ARG NEXT_PUBLIC_DEFAULT_LOCALE=en +ENV NEXT_PUBLIC_URL=$NEXT_PUBLIC_URL +ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME +ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE + +RUN npx turbo run build --filter=@claudemesh/web... --filter=web... + +# Stage 2: runtime — standalone output only +FROM node:22-slim AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public + +USER nextjs +EXPOSE 3000 + +HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \ + CMD node -e "fetch('http://localhost:3000').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" + +CMD ["node", "apps/web/server.js"]