Files
stack/apps/orchestrator/Dockerfile
Jason Woltje 0271ec1e49
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
fix(orchestrator): copy schema file after COPY to overwrite dangling symlink
Kaniko does NOT follow symlinks — it copies them as-is. The symlink
apps/orchestrator/prisma/schema.prisma → ../../api/prisma/schema.prisma
becomes dangling inside the container since apps/api/ is not present.

Fix: explicitly copy apps/api/prisma/schema.prisma to
apps/orchestrator/prisma/schema.prisma AFTER the COPY apps/orchestrator
step. This overwrites the symlink with the actual file content.

CI still works via the symlink (full monorepo checkout resolves it).
Docker now has the real file at the expected path.
2026-03-07 10:45:44 -06:00

121 lines
4.7 KiB
Docker

# Base image for all stages
# Uses Debian slim (glibc) instead of Alpine (musl) for native addon compatibility.
FROM git.mosaicstack.dev/mosaic/node-base:24-slim AS base
# Install pnpm globally
RUN corepack enable && corepack prepare pnpm@10.27.0 --activate
# Set working directory
WORKDIR /app
# Copy monorepo configuration files
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
COPY turbo.json ./
# ======================
# Dependencies stage
# ======================
FROM base AS deps
# Copy all package.json files for workspace resolution
COPY packages/shared/package.json ./packages/shared/
COPY packages/config/package.json ./packages/config/
COPY apps/orchestrator/package.json ./apps/orchestrator/
# API schema is available via apps/orchestrator/prisma/schema.prisma symlink
# Copy npm configuration for native binary architecture hints
COPY .npmrc ./
# Install ALL dependencies (not just production)
# No cache mount — Kaniko builds are ephemeral in CI
RUN pnpm install --frozen-lockfile
# ======================
# Builder stage
# ======================
FROM base AS builder
# Copy root node_modules from deps
COPY --from=deps /app/node_modules ./node_modules
# Copy all source code
COPY packages ./packages
COPY apps/orchestrator ./apps/orchestrator
# Copy workspace node_modules from deps
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
COPY --from=deps /app/packages/config/node_modules ./packages/config/node_modules
COPY --from=deps /app/apps/orchestrator/node_modules ./apps/orchestrator/node_modules
# Overwrite the symlink (apps/orchestrator/prisma/schema.prisma → ../../api/prisma/schema.prisma)
# with the real file content. Kaniko copies symlinks AS symlinks (does not follow them),
# so the symlink target would be dangling inside the container. Copying the actual file
# after the orchestrator COPY overwrites the symlink with the resolved content.
COPY apps/api/prisma/schema.prisma ./apps/orchestrator/prisma/schema.prisma
# pnpm turbo build runs prisma:generate (--schema=./prisma/schema.prisma) from the
# orchestrator package context — no cross-package project-root issues.
# Build the orchestrator app using TurboRepo
RUN pnpm turbo build --filter=@mosaic/orchestrator
# Remove compiled test/spec files from dist BEFORE copying to production.
# These contain test fixture secrets (fake AWS keys, RSA keys) that trigger Trivy.
# Must happen in builder stage so they never appear in any production image layer.
RUN find ./apps/orchestrator/dist \( -name '*.spec.js' -o -name '*.spec.js.map' -o -name '*.test.js' -o -name '*.test.js.map' \) -delete
# ======================
# Production stage
# ======================
FROM git.mosaicstack.dev/mosaic/node-base:24-slim AS production
# Add metadata labels
LABEL maintainer="mosaic-team@mosaicstack.dev"
LABEL version="0.0.6"
LABEL description="Mosaic Orchestrator - Agent orchestration service"
LABEL org.opencontainers.image.source="https://git.mosaicstack.dev/mosaic/stack"
LABEL org.opencontainers.image.vendor="Mosaic Stack"
LABEL org.opencontainers.image.title="Mosaic Orchestrator"
LABEL org.opencontainers.image.description="Agent orchestration service for Mosaic Stack"
# dumb-init, ca-certificates pre-installed in base image
# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot)
# - Remove npm/npx to reduce image size (not used in production)
# - Create non-root user
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
&& groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
WORKDIR /app
# Copy node_modules from builder (includes all dependencies)
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
# Copy built packages (includes dist/ directories)
COPY --from=builder --chown=nestjs:nodejs /app/packages ./packages
# Copy built orchestrator application (spec/test files already removed in builder stage)
COPY --from=builder --chown=nestjs:nodejs /app/apps/orchestrator/dist ./apps/orchestrator/dist
COPY --from=builder --chown=nestjs:nodejs /app/apps/orchestrator/package.json ./apps/orchestrator/
# Copy app's node_modules which contains symlinks to root node_modules
COPY --from=builder --chown=nestjs:nodejs /app/apps/orchestrator/node_modules ./apps/orchestrator/node_modules
# Set working directory to orchestrator app
WORKDIR /app/apps/orchestrator
# Switch to non-root user
USER nestjs
# Expose orchestrator port
EXPOSE 3001
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
# Start the application
CMD ["node", "dist/main.js"]