From 6012e8312b7c8f4ff66ebe90058ad2e9a9333ef1 Mon Sep 17 00:00:00 2001 From: Kacper Date: Sun, 28 Dec 2025 19:57:22 +0100 Subject: [PATCH] refactor: consolidate Dockerfiles into single multi-stage build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create unified Dockerfile with multi-stage builds (base, server, ui targets) - Centralize lib package.json COPYs in shared base stage (DRY) - Add Claude CLI installation for Docker authentication support - Remove duplicate apps/server/Dockerfile and apps/ui/Dockerfile - Update docker-compose.yml to use target: parameter - Add docker-compose.override.yml to .gitignore Build commands: docker build --target server -t automaker-server . docker build --target ui -t automaker-ui . docker-compose build && docker-compose up -d 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 2 +- Dockerfile | 143 +++++++++++++++++++++++++++++++++++++++++ apps/server/Dockerfile | 75 --------------------- apps/ui/Dockerfile | 51 --------------- docker-compose.yml | 6 +- 5 files changed, 148 insertions(+), 129 deletions(-) create mode 100644 Dockerfile delete mode 100644 apps/server/Dockerfile delete mode 100644 apps/ui/Dockerfile diff --git a/.gitignore b/.gitignore index 5efd9e1f..d35ec2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -80,4 +80,4 @@ blob-report/ *.pem docker-compose.override.yml -.claude/ \ No newline at end of file +.claude/docker-compose.override.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..1b6bb0a7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,143 @@ +# Automaker Multi-Stage Dockerfile +# Single Dockerfile for both server and UI builds +# Usage: +# docker build --target server -t automaker-server . +# docker build --target ui -t automaker-ui . +# Or use docker-compose which selects targets automatically + +# ============================================================================= +# BASE STAGE - Common setup for all builds (DRY: defined once, used by all) +# ============================================================================= +FROM node:20-alpine AS base + +# Install build dependencies for native modules (node-pty) +RUN apk add --no-cache python3 make g++ + +WORKDIR /app + +# Copy root package files +COPY package*.json ./ + +# Copy all libs package.json files (centralized - add new libs here) +COPY libs/types/package*.json ./libs/types/ +COPY libs/utils/package*.json ./libs/utils/ +COPY libs/prompts/package*.json ./libs/prompts/ +COPY libs/platform/package*.json ./libs/platform/ +COPY libs/model-resolver/package*.json ./libs/model-resolver/ +COPY libs/dependency-resolver/package*.json ./libs/dependency-resolver/ +COPY libs/git-utils/package*.json ./libs/git-utils/ + +# Copy scripts (needed by npm workspace) +COPY scripts ./scripts + +# ============================================================================= +# SERVER BUILD STAGE +# ============================================================================= +FROM base AS server-builder + +# Copy server-specific package.json +COPY apps/server/package*.json ./apps/server/ + +# Install dependencies (--ignore-scripts to skip husky/prepare, then rebuild native modules) +RUN npm ci --ignore-scripts && npm rebuild node-pty + +# Copy all source files +COPY libs ./libs +COPY apps/server ./apps/server + +# Build packages in dependency order, then build server +RUN npm run build:packages && npm run build --workspace=apps/server + +# ============================================================================= +# SERVER PRODUCTION STAGE +# ============================================================================= +FROM node:20-alpine AS server + +# Install git, curl, and GitHub CLI (pinned version for reproducible builds) +RUN apk add --no-cache git curl && \ + GH_VERSION="2.63.2" && \ + curl -L "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o gh.tar.gz && \ + tar -xzf gh.tar.gz && \ + mv "gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh && \ + rm -rf gh.tar.gz "gh_${GH_VERSION}_linux_amd64" + +# Install Claude CLI globally +RUN npm install -g @anthropic-ai/claude-code + +WORKDIR /app + +# Create non-root user +RUN addgroup -g 1001 -S automaker && \ + adduser -S automaker -u 1001 + +# Copy root package.json (needed for workspace resolution) +COPY --from=server-builder /app/package*.json ./ + +# Copy built libs (workspace packages are symlinked in node_modules) +COPY --from=server-builder /app/libs ./libs + +# Copy built server +COPY --from=server-builder /app/apps/server/dist ./apps/server/dist +COPY --from=server-builder /app/apps/server/package*.json ./apps/server/ + +# Copy node_modules (includes symlinks to libs) +COPY --from=server-builder /app/node_modules ./node_modules + +# Create data and projects directories +RUN mkdir -p /data /projects && chown automaker:automaker /data /projects + +# Switch to non-root user +USER automaker + +# Environment variables +ENV NODE_ENV=production +ENV PORT=3008 +ENV DATA_DIR=/data + +# Expose port +EXPOSE 3008 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3008/api/health || exit 1 + +# Start server +CMD ["node", "apps/server/dist/index.js"] + +# ============================================================================= +# UI BUILD STAGE +# ============================================================================= +FROM base AS ui-builder + +# Copy UI-specific package.json +COPY apps/ui/package*.json ./apps/ui/ + +# Install dependencies (--ignore-scripts to skip husky and build:packages in prepare script) +RUN npm ci --ignore-scripts + +# Copy all source files +COPY libs ./libs +COPY apps/ui ./apps/ui + +# Build packages in dependency order, then build UI +# VITE_SERVER_URL tells the UI where to find the API server +# Use ARG to allow overriding at build time: --build-arg VITE_SERVER_URL=http://api.example.com +ARG VITE_SERVER_URL=http://localhost:3008 +ENV VITE_SKIP_ELECTRON=true +ENV VITE_SERVER_URL=${VITE_SERVER_URL} +RUN npm run build:packages && npm run build --workspace=apps/ui + +# ============================================================================= +# UI PRODUCTION STAGE +# ============================================================================= +FROM nginx:alpine AS ui + +# Copy built files +COPY --from=ui-builder /app/apps/ui/dist /usr/share/nginx/html + +# Copy nginx config for SPA routing +COPY apps/ui/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile deleted file mode 100644 index 981bcd10..00000000 --- a/apps/server/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ -# Automaker Backend Server -# Multi-stage build for minimal production image - -# Build stage -FROM node:20-alpine AS builder - -# Install build dependencies for native modules (node-pty) -RUN apk add --no-cache python3 make g++ - -WORKDIR /app - -# Copy package files for all workspaces -COPY package*.json ./ -COPY apps/server/package*.json ./apps/server/ -COPY libs/types/package*.json ./libs/types/ -COPY libs/utils/package*.json ./libs/utils/ -COPY libs/prompts/package*.json ./libs/prompts/ -COPY libs/platform/package*.json ./libs/platform/ -COPY libs/model-resolver/package*.json ./libs/model-resolver/ -COPY libs/dependency-resolver/package*.json ./libs/dependency-resolver/ -COPY libs/git-utils/package*.json ./libs/git-utils/ -COPY scripts ./scripts - -# Install dependencies (--ignore-scripts to skip husky and build:packages in prepare script) -RUN npm ci --ignore-scripts - -# Copy all source files -COPY libs ./libs -COPY apps/server ./apps/server - -# Build packages in dependency order, then build server -RUN npm run build:packages && npm run build --workspace=apps/server - -# Production stage -FROM node:20-alpine - -# Install git, curl, and GitHub CLI (pinned version for reproducible builds) -RUN apk add --no-cache git curl && \ - GH_VERSION="2.63.2" && \ - curl -L "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o gh.tar.gz && \ - tar -xzf gh.tar.gz && \ - mv "gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh && \ - rm -rf gh.tar.gz "gh_${GH_VERSION}_linux_amd64" - -WORKDIR /app - -# Create non-root user -RUN addgroup -g 1001 -S automaker && \ - adduser -S automaker -u 1001 - -# Copy built files and production dependencies -COPY --from=builder /app/apps/server/dist ./dist -COPY --from=builder /app/apps/server/package*.json ./ -COPY --from=builder /app/node_modules ./node_modules - -# Create data directory -RUN mkdir -p /data && chown automaker:automaker /data - -# Switch to non-root user -USER automaker - -# Environment variables -ENV NODE_ENV=production -ENV PORT=3008 -ENV DATA_DIR=/data - -# Expose port -EXPOSE 3008 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:3008/api/health || exit 1 - -# Start server -CMD ["node", "dist/index.js"] diff --git a/apps/ui/Dockerfile b/apps/ui/Dockerfile deleted file mode 100644 index dbfd16d9..00000000 --- a/apps/ui/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -# Automaker UI -# Multi-stage build for minimal production image - -# Build stage -FROM node:20-alpine AS builder - -# Install build dependencies -RUN apk add --no-cache python3 make g++ - -WORKDIR /app - -# Copy package files for all workspaces -COPY package*.json ./ -COPY apps/ui/package*.json ./apps/ui/ -COPY libs/types/package*.json ./libs/types/ -COPY libs/utils/package*.json ./libs/utils/ -COPY libs/prompts/package*.json ./libs/prompts/ -COPY libs/platform/package*.json ./libs/platform/ -COPY libs/model-resolver/package*.json ./libs/model-resolver/ -COPY libs/dependency-resolver/package*.json ./libs/dependency-resolver/ -COPY libs/git-utils/package*.json ./libs/git-utils/ -COPY scripts ./scripts - -# Install dependencies (--ignore-scripts to skip husky and build:packages in prepare script) -RUN npm ci --ignore-scripts - -# Copy all source files -COPY libs ./libs -COPY apps/ui ./apps/ui - -# Build packages in dependency order, then build UI -# VITE_SERVER_URL tells the UI where to find the API server -# Using localhost:3008 since both containers expose ports to the host -# Use ARG to allow overriding at build time: --build-arg VITE_SERVER_URL=http://api.example.com -ARG VITE_SERVER_URL=http://localhost:3008 -ENV VITE_SKIP_ELECTRON=true -ENV VITE_SERVER_URL=${VITE_SERVER_URL} -RUN npm run build:packages && npm run build --workspace=apps/ui - -# Production stage - serve with nginx -FROM nginx:alpine - -# Copy built files -COPY --from=builder /app/apps/ui/dist /usr/share/nginx/html - -# Copy nginx config for SPA routing -COPY apps/ui/nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.yml b/docker-compose.yml index 0cbc28c2..bdf5ff19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,8 @@ services: ui: build: context: . - dockerfile: apps/ui/Dockerfile + dockerfile: Dockerfile + target: ui container_name: automaker-ui restart: unless-stopped ports: @@ -25,7 +26,8 @@ services: server: build: context: . - dockerfile: apps/server/Dockerfile + dockerfile: Dockerfile + target: server container_name: automaker-server restart: unless-stopped ports: