mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 21:23:07 +00:00
Merge pull request #302 from AutoMaker-Org/fix/docker-build
refactor: update Dockerfiles for server and UI to streamline dependen…
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -80,4 +80,4 @@ blob-report/
|
|||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
.claude/
|
.claude/docker-compose.override.yml
|
||||||
|
|||||||
155
Dockerfile
Normal file
155
Dockerfile
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# 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:22-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:22-alpine AS server
|
||||||
|
|
||||||
|
# Install git, curl, bash (for terminal), and GitHub CLI (pinned version, multi-arch)
|
||||||
|
RUN apk add --no-cache git curl bash && \
|
||||||
|
GH_VERSION="2.63.2" && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
case "$ARCH" in \
|
||||||
|
x86_64) GH_ARCH="amd64" ;; \
|
||||||
|
aarch64|arm64) GH_ARCH="arm64" ;; \
|
||||||
|
*) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
|
||||||
|
esac && \
|
||||||
|
curl -L "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${GH_ARCH}.tar.gz" -o gh.tar.gz && \
|
||||||
|
tar -xzf gh.tar.gz && \
|
||||||
|
mv gh_${GH_VERSION}_linux_${GH_ARCH}/bin/gh /usr/local/bin/gh && \
|
||||||
|
rm -rf gh.tar.gz gh_${GH_VERSION}_linux_${GH_ARCH}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Configure git for mounted volumes and authentication
|
||||||
|
# Use --system so it's not overwritten by mounted user .gitconfig
|
||||||
|
RUN git config --system --add safe.directory '*' && \
|
||||||
|
# Use gh as credential helper (works with GH_TOKEN env var)
|
||||||
|
git config --system credential.helper '!gh auth git-credential'
|
||||||
|
|
||||||
|
# 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 (using curl since it's already installed, more reliable than busybox wget)
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f 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;"]
|
||||||
101
README.md
101
README.md
@@ -223,14 +223,111 @@ npm run build:electron:linux # Linux (AppImage + DEB, x64)
|
|||||||
|
|
||||||
#### Docker Deployment
|
#### Docker Deployment
|
||||||
|
|
||||||
|
Docker provides the most secure way to run Automaker by isolating it from your host filesystem.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build and run with Docker Compose (recommended for security)
|
# Build and run with Docker Compose
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# Access at http://localhost:3007
|
# Access UI at http://localhost:3007
|
||||||
# API at http://localhost:3008
|
# API at http://localhost:3008
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop containers
|
||||||
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Configuration
|
||||||
|
|
||||||
|
Create a `.env` file in the project root if using API key authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Optional: Anthropic API key (not needed if using Claude CLI authentication)
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Most users authenticate via Claude CLI instead of API keys. See [Claude CLI Authentication](#claude-cli-authentication-optional) below.
|
||||||
|
|
||||||
|
##### Working with Projects (Host Directory Access)
|
||||||
|
|
||||||
|
By default, the container is isolated from your host filesystem. To work on projects from your host machine, create a `docker-compose.override.yml` file (gitignored):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
volumes:
|
||||||
|
# Mount your project directories
|
||||||
|
- /path/to/your/project:/projects/your-project
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Claude CLI Authentication (Optional)
|
||||||
|
|
||||||
|
To use Claude Code CLI authentication instead of an API key, mount your Claude CLI config directory:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
volumes:
|
||||||
|
# Linux/macOS
|
||||||
|
- ~/.claude:/home/automaker/.claude
|
||||||
|
# Windows
|
||||||
|
- C:/Users/YourName/.claude:/home/automaker/.claude
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The Claude CLI config must be writable (do not use `:ro` flag) as the CLI writes debug files.
|
||||||
|
|
||||||
|
##### GitHub CLI Authentication (For Git Push/PR Operations)
|
||||||
|
|
||||||
|
To enable git push and GitHub CLI operations inside the container:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
volumes:
|
||||||
|
# Mount GitHub CLI config
|
||||||
|
# Linux/macOS
|
||||||
|
- ~/.config/gh:/home/automaker/.config/gh
|
||||||
|
# Windows
|
||||||
|
- 'C:/Users/YourName/AppData/Roaming/GitHub CLI:/home/automaker/.config/gh'
|
||||||
|
|
||||||
|
# Mount git config for user identity (name, email)
|
||||||
|
- ~/.gitconfig:/home/automaker/.gitconfig:ro
|
||||||
|
environment:
|
||||||
|
# GitHub token (required on Windows where tokens are in Credential Manager)
|
||||||
|
# Get your token with: gh auth token
|
||||||
|
- GH_TOKEN=${GH_TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add `GH_TOKEN` to your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GH_TOKEN=gho_your_github_token_here
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Complete docker-compose.override.yml Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
volumes:
|
||||||
|
# Your projects
|
||||||
|
- /path/to/project1:/projects/project1
|
||||||
|
- /path/to/project2:/projects/project2
|
||||||
|
|
||||||
|
# Authentication configs
|
||||||
|
- ~/.claude:/home/automaker/.claude
|
||||||
|
- ~/.config/gh:/home/automaker/.config/gh
|
||||||
|
- ~/.gitconfig:/home/automaker/.gitconfig:ro
|
||||||
|
environment:
|
||||||
|
- GH_TOKEN=${GH_TOKEN}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Architecture Support
|
||||||
|
|
||||||
|
The Docker image supports both AMD64 and ARM64 architectures. The GitHub CLI and Claude CLI are automatically downloaded for the correct architecture during build.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
#### End-to-End Tests (Playwright)
|
#### End-to-End Tests (Playwright)
|
||||||
|
|||||||
@@ -1,67 +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 and scripts needed for postinstall
|
|
||||||
COPY package*.json ./
|
|
||||||
COPY apps/server/package*.json ./apps/server/
|
|
||||||
COPY scripts ./scripts
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci --workspace=apps/server
|
|
||||||
|
|
||||||
# Copy source
|
|
||||||
COPY apps/server ./apps/server
|
|
||||||
|
|
||||||
# Build TypeScript
|
|
||||||
RUN 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"]
|
|
||||||
@@ -94,10 +94,26 @@ async function getGhStatus(): Promise<GhStatus> {
|
|||||||
// Version command failed
|
// Version command failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication status
|
// Check authentication status by actually making an API call
|
||||||
|
// gh auth status can return non-zero even when GH_TOKEN is valid
|
||||||
|
let apiCallSucceeded = false;
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync('gh api user --jq ".login"', { env: execEnv });
|
||||||
|
const user = stdout.trim();
|
||||||
|
if (user) {
|
||||||
|
status.authenticated = true;
|
||||||
|
status.user = user;
|
||||||
|
apiCallSucceeded = true;
|
||||||
|
}
|
||||||
|
// If stdout is empty, fall through to gh auth status fallback
|
||||||
|
} catch {
|
||||||
|
// API call failed - fall through to gh auth status fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: try gh auth status if API call didn't succeed
|
||||||
|
if (!apiCallSucceeded) {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync('gh auth status', { env: execEnv });
|
const { stdout } = await execAsync('gh auth status', { env: execEnv });
|
||||||
// If this succeeds without error, we're authenticated
|
|
||||||
status.authenticated = true;
|
status.authenticated = true;
|
||||||
|
|
||||||
// Try to extract username from output
|
// Try to extract username from output
|
||||||
@@ -107,10 +123,8 @@ async function getGhStatus(): Promise<GhStatus> {
|
|||||||
if (userMatch) {
|
if (userMatch) {
|
||||||
status.user = userMatch[1];
|
status.user = userMatch[1];
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch {
|
||||||
// Auth status returns non-zero if not authenticated
|
// Auth status returns non-zero if not authenticated
|
||||||
const err = error as { stderr?: string };
|
|
||||||
if (err.stderr?.includes('not logged in')) {
|
|
||||||
status.authenticated = false;
|
status.authenticated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +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
|
|
||||||
COPY package*.json ./
|
|
||||||
COPY apps/ui/package*.json ./apps/ui/
|
|
||||||
COPY scripts ./scripts
|
|
||||||
|
|
||||||
# Install dependencies (skip electron postinstall)
|
|
||||||
RUN npm ci --workspace=apps/ui --ignore-scripts
|
|
||||||
|
|
||||||
# Copy source
|
|
||||||
COPY apps/ui ./apps/ui
|
|
||||||
|
|
||||||
# Build for web (skip electron)
|
|
||||||
# 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 --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;"]
|
|
||||||
@@ -13,7 +13,8 @@ services:
|
|||||||
ui:
|
ui:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: apps/ui/Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: ui
|
||||||
container_name: automaker-ui
|
container_name: automaker-ui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
@@ -25,7 +26,8 @@ services:
|
|||||||
server:
|
server:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: apps/server/Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: server
|
||||||
container_name: automaker-server
|
container_name: automaker-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
Reference in New Issue
Block a user