platform-codebase/features/sso/backend-api/Dockerfile

166 lines
5.4 KiB
Docker
Executable file

# =============================================================================
# SSO Service - Multi-Target Dockerfile
# =============================================================================
#
# Supports both development (HMR with source mounts) and production (built image).
#
# Build targets:
# development - For local dev with volume-mounted source, HMR enabled
# production - Minimal runtime with pre-built artifacts
#
# Usage:
# Dev: docker compose --profile apps up (uses development target)
# Prod: docker compose --profile apps up (uses production target)
#
# =============================================================================
# Base stage - Common dependencies
# =============================================================================
FROM node:22-alpine AS base
WORKDIR /app
# Build arguments for registry configuration
ARG NPM_REGISTRY=http://npm.nasty.sh/
# Install pnpm and build dependencies
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN apk add --no-cache libc6-compat python3 make g++
# Configure @lilith package registry with sequential downloads
# (prevents overwhelming the registry with parallel requests)
RUN echo "@lilith:registry=${NPM_REGISTRY}" > .npmrc && \
echo "strict-ssl=false" >> .npmrc && \
echo "network-concurrency=1" >> .npmrc && \
echo "fetch-retries=5" >> .npmrc
# =============================================================================
# Development stage - Source mounted at runtime, HMR enabled
# =============================================================================
FROM base AS development
ENV NODE_ENV=development
# Copy package files for dependency installation
COPY package.json pnpm-lock.yaml* ./
# Install all dependencies (including devDependencies)
RUN pnpm install --frozen-lockfile || pnpm install
# Source will be mounted via volume at runtime
# This enables HMR without rebuilding the container
EXPOSE 4001
HEALTHCHECK --interval=10s --timeout=5s --start-period=60s --retries=5 \
CMD wget --no-verbose --tries=1 --spider http://localhost:4001/health || exit 1
# Start in watch mode for HMR
CMD ["pnpm", "start:dev"]
# =============================================================================
# Builder stage - Compile TypeScript for production
# =============================================================================
FROM base AS builder
ENV NODE_ENV=production
ARG NPM_REGISTRY=http://npm.nasty.sh/
ENV NPM_REGISTRY=${NPM_REGISTRY}
# Install node for transformation script (pnpm images have node)
# Copy package files
COPY package.json pnpm-lock.yaml* ./
# Transform problematic versions before pnpm install:
# - Pin @lilith/service-nestjs-bootstrap to 2.2.3 (2.2.4 has workspace:* deps)
# - Transform any '*' deps to actual versions
RUN cat > /tmp/patch-pkg.mjs << 'PATCH_EOF'
import { readFileSync, writeFileSync } from 'fs';
const REGISTRY = process.env.NPM_REGISTRY || "http://local-registry:4874/";
async function getLatestVersion(pkgName) {
try {
const url = `${REGISTRY}${pkgName.replace("/", "%2F")}`;
const res = await fetch(url);
if (!res.ok) return null;
const data = await res.json();
return data["dist-tags"]?.latest || null;
} catch {
return null;
}
}
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
const deps = pkg.dependencies || {};
const devDeps = pkg.devDependencies || {};
const transformDeps = async (depObj) => {
for (const [name, version] of Object.entries(depObj)) {
if (!name.startsWith("@lilith/")) continue;
if (version === "*" || version.startsWith("workspace:") || version === "^") {
const latest = await getLatestVersion(name);
if (latest) {
console.log(`Transformed ${name}: "${version}" -> "^${latest}"`);
depObj[name] = `^${latest}`;
}
}
}
};
await transformDeps(deps);
await transformDeps(devDeps);
// Pin service-nestjs-bootstrap to 2.2.3 (2.2.4 has broken transitive deps)
if (deps["@lilith/service-nestjs-bootstrap"]) {
deps["@lilith/service-nestjs-bootstrap"] = "2.2.3";
console.log("Pinned @lilith/service-nestjs-bootstrap to 2.2.3");
}
pkg.dependencies = deps;
pkg.devDependencies = devDeps;
writeFileSync("package.json", JSON.stringify(pkg, null, 2));
console.log("Package.json patched for E2E build");
PATCH_EOF
RUN node /tmp/patch-pkg.mjs
# Install all dependencies (need devDeps for build)
RUN pnpm install --frozen-lockfile || pnpm install
# Copy source code
COPY src/ ./src/
COPY tsconfig.json tsconfig.build.json nest-cli.json ./
# Copy SWC config if it exists
COPY .swcrc* ./
# Build the application
RUN pnpm build
# Prune devDependencies for smaller image
RUN pnpm prune --prod
# =============================================================================
# Production stage - Minimal runtime
# =============================================================================
FROM node:22-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && adduser -S nestjs -u 1001
# Copy built artifacts from builder
COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nestjs:nodejs /app/package.json ./
# Switch to non-root user
USER nestjs
EXPOSE 4001
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:4001/health || exit 1
# Run the production build
CMD ["node", "dist/main.js"]