# Start with a base image and install system dependencies # Pinned to Node 24 because the monorepo enforces ">=20 <25" in package.json engines. FROM node:24-alpine AS base RUN apk add \ graphicsmagick \ imagemagick \ ghostscript \ postgresql-client \ redis \ curl \ nano \ bash WORKDIR /app # Stage for installing dependencies (cache-friendly) FROM base AS deps # Copy package files for both root and server parts of the project COPY package.json package-lock.json ./ COPY server/package.json ./server/ COPY shared/package.json ./shared/ COPY ee/packages/workflows/package.json ./ee/packages/workflows/ COPY tsconfig.base.json ./ COPY server/src/invoice-templates/assemblyscript ./server/src/invoice-templates/assemblyscript # Builder stage for compiling the application FROM deps AS builder # Fallback secret values ensure edge-auth guards pass during build; real values come from runtime secrets. ARG NEXTAUTH_SECRET_BUILD="dev-placeholder-nextauth-secret-32" ARG NEXT_BUILD_MAX_OLD_SPACE_SIZE="12288" # Keep every Node-based build step in this stage on the same heap limit. The # set-image-tag.sh helper passes NEXT_BUILD_MAX_OLD_SPACE_SIZE as a build arg. ENV NODE_OPTIONS="--max-old-space-size=${NEXT_BUILD_MAX_OLD_SPACE_SIZE}" # Copy all project files and build the server WORKDIR /app COPY . . # Remove only the EE server tree for CE builds. # The workflows workspace now lives under ee/packages/workflows and must remain # present because node_modules/@alga-psa/workflows is a workspace symlink. RUN rm -rf /app/ee/server # Copy CE stubs from server/src/empty to ee/server/src for @ee alias resolution # This ensures both TypeScript and webpack can resolve @ee imports during CE builds RUN mkdir -p /app/ee/server/src && \ cp -r /app/server/src/empty/. /app/ee/server/src/ # Create a dummy file for the relative import that webpack tries to resolve RUN mkdir -p /app/ee/server/src/lib/extensions && \ echo "export const initializeExtensions = async () => { console.log('CE build - extensions not available'); };" > /app/ee/server/src/lib/extensions/initialize.js # Create minimal stubs for packages that use relative paths (not @ee alias) # packages/product-chat/ee/entry.tsx uses ../../../ee/server/src/services/* RUN mkdir -p /app/ee/server/src/services && \ printf "export class ChatStreamService {\n static async handleChatStream(req) {\n return new Response(JSON.stringify({ error: 'Chat streaming is only available in Enterprise Edition' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n static async handleTitleStream(req) {\n return new Response(JSON.stringify({ error: 'Chat streaming is only available in Enterprise Edition' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n" > /app/ee/server/src/services/chatStreamService.js && \ printf "export class TemporaryApiKeyService {\n static async cleanupExpiredAiKeys() {\n return 0;\n }\n}\n" > /app/ee/server/src/services/temporaryApiKeyService.js && \ printf "export class ChatCompletionsService {\n static async handleRequest(req) {\n return new Response(JSON.stringify({ error: 'Chat completions are only available in Enterprise Edition' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n static async handleExecute(req) {\n return new Response(JSON.stringify({ error: 'Chat completions are only available in Enterprise Edition' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n}\n" > /app/ee/server/src/services/chatCompletionsService.js # Remove EE-specific scripts that shouldn't be in CE build RUN rm -f /app/server/src/scripts/check-extension.ts RUN npm install --include=optional # Build all upstream packages (shared + pre-built) via Nx (handles dependency ordering) WORKDIR /app RUN npx nx build-deps server ENV USE_PREBUILT="true" # Set edition to community for proper module resolution ENV EDITION="ce" ENV NEXT_PUBLIC_EDITION="community" WORKDIR /app/server RUN NEXTAUTH_SECRET="${NEXTAUTH_SECRET_BUILD}" \ AUTH_SECRET="${NEXTAUTH_SECRET_BUILD}" \ NODE_ENV=production \ npm exec -- next build --webpack # Create secrets directory and populate with secure placeholder values RUN mkdir -p /app/secrets && \ echo "secure-admin-password-placeholder" > /app/secrets/postgres_password && \ echo "secure-app-password-placeholder" > /app/secrets/db_password_server && \ echo "secure-hocuspocus-password-placeholder" > /app/secrets/db_password_hocuspocus && \ echo "secure-redis-password-placeholder" > /app/secrets/redis_password && \ echo "secure-32char-auth-key-placeholder-xxxxx" > /app/secrets/alga_auth_key && \ echo "secure-32char-crypto-key-placeholder-xxxx" > /app/secrets/crypto_key && \ echo "secure-32char-token-key-placeholder-xxxx" > /app/secrets/token_secret_key && \ echo "secure-32char-nextauth-key-placeholder-xx" > /app/secrets/nextauth_secret && \ echo "secure-email-password-placeholder" > /app/secrets/email_password && \ echo "secure-oauth-client-id-placeholder" > /app/secrets/google_oauth_client_id && \ echo "secure-oauth-client-secret-placeholder" > /app/secrets/google_oauth_client_secret && \ echo "secure-gmail-client-id-placeholder" > /app/secrets/GOOGLE_CLIENT_ID && \ echo "secure-gmail-client-secret-placeholder" > /app/secrets/GOOGLE_CLIENT_SECRET && \ echo "secure-ms-client-id-placeholder" > /app/secrets/MICROSOFT_CLIENT_ID && \ echo "secure-ms-client-secret-placeholder" > /app/secrets/MICROSOFT_CLIENT_SECRET && \ chmod 600 /app/secrets/* # Copy example environment file COPY .env.example /app/.env COPY .env.example /app/server/.env # Final production image with minimal runtime artifacts FROM node:24-alpine RUN apk add --no-cache bash \ postgresql-client \ redis \ graphicsmagick \ imagemagick \ ghostscript \ curl \ nano \ bash WORKDIR /app COPY tsconfig.base.json ./ COPY server/setup /app/server/setup COPY .env.example /app/.env COPY .env.example /app/server/.env # Copy built application and node_modules from earlier stages -- minimalist approach COPY --from=builder /app/shared ./shared COPY --from=builder /app/ee/packages/workflows ./ee/packages/workflows COPY --from=builder /app/server/.next ./server/.next COPY --from=builder /app/server/public ./server/public COPY --from=builder /app/server/next.config.mjs ./server/ COPY --from=builder /app/server/package.json ./server/ COPY --from=builder /app/package.json ./ COPY --from=builder /app/package-lock.json ./ COPY --from=builder /app/server/knexfile.cjs ./server/ COPY --from=builder /app/server/tsconfig.json ./server/ COPY --from=builder /app/server/index.ts ./server/ COPY --from=builder /app/server/migrations/ ./server/migrations/ COPY --from=builder /app/server/seeds/ ./server/seeds/ COPY --from=builder /app/server/src/ ./server/src/ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/server/node_modules ./server/node_modules RUN npm install -g tsx # Copy core package.json for version info COPY packages/core/package.json /app/packages/core/package.json COPY server/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh EXPOSE 3000 # Environment configuration ENV NODE_ENV=production # Secret provider configuration # Default configuration for composite secret system # Can be overridden in docker-compose or deployment environments ENV SECRET_READ_CHAIN="env,filesystem" ENV SECRET_WRITE_PROVIDER="filesystem" ENTRYPOINT ["/app/entrypoint.sh"]