From cf5c5b72ce7e700c65d8d3c54cf7a3ebf987e0fb Mon Sep 17 00:00:00 2001 From: Faris Hijazi Date: Sat, 22 Nov 2025 13:57:32 +0300 Subject: [PATCH 1/2] feat: add Docker deployment support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add production-ready Docker deployment with the following features: - **Multi-stage Dockerfile**: Optimized build with separate builder and production stages - **Docker Compose**: Full orchestration with Traefik integration for automatic routing - **Security**: Runs as non-root user (node:node) with minimal runtime dependencies - **Health Checks**: Built-in health monitoring every 30 seconds - **Volume Persistence**: Separate volumes for data and configuration - **Network Isolation**: Dedicated network with Traefik integration via public network - **LinuxServer.io Standards**: Follows container best practices for production deployment Configuration: - Internal port: 3001 - External port: 3002 (configurable via EXTERNAL_PORT env var) - Traefik labels for automatic routing to claudecodeui.fhijazi.com - WebSocket support for real-time communication - Read-only mount of ~/.claude for project discovery Build and deployment tested successfully with health checks passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .dockerignore | 46 +++++++++++++++++++++++++++++ Dockerfile | 72 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 59 +++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0deaf533 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Development +.git/ +.github/ +.vscode/ +.idea/ + +# Build outputs +dist/ +build/ + +# Environment files +.env.local +.env.development +.env.test + +# Testing +coverage/ +.nyc_output/ + +# Misc +*.log +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Documentation +*.md +!README.md + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# Docker +Dockerfile* +docker-compose* +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..373e1332 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +FROM node:20-alpine AS builder + +# Install build dependencies +RUN apk add --no-cache \ + python3 \ + make \ + g++ \ + git + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including dev dependencies for building) +RUN npm ci && \ + npm cache clean --force + +# Copy source code +COPY . . + +# Build frontend +RUN npm run build + +# ============================================================================= +# Production stage +# ============================================================================= +FROM node:20-alpine + +# Install runtime dependencies +RUN apk add --no-cache \ + python3 \ + make \ + g++ \ + git \ + ca-certificates \ + tzdata + +WORKDIR /app + +# Copy package files and install production dependencies +COPY package*.json ./ +RUN npm ci --only=production && \ + npm cache clean --force + +# Copy built files from builder +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/server ./server +COPY --from=builder /app/public ./public +COPY --from=builder /app/index.html ./index.html + +# Create necessary directories with proper permissions +RUN mkdir -p /data /config && \ + chown -R node:node /app /data /config + +# Switch to node user (uid/gid 1000) +USER node + +# Environment variables +ENV NODE_ENV=production \ + PORT=3001 \ + DATABASE_PATH=/data/auth.db + +# Expose port +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3001/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +# Start server +CMD ["node", "server/index.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..e555dd0e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +networks: + claudecodeui: + name: claudecodeui + public: + external: true + +services: + claudecodeui: + container_name: claudecodeui + build: + context: . + dockerfile: Dockerfile + image: claudecodeui:latest + restart: unless-stopped + ports: + - "${EXTERNAL_PORT:-3002}:3001" + env_file: + - path: ./.env + required: true + environment: + - PORT=${PORT:-3001} + - DATABASE_PATH=${DATABASE_PATH:-/data/auth.db} + - CONTEXT_WINDOW=${CONTEXT_WINDOW:-160000} + - CLAUDE_CLI_PATH=${CLAUDE_CLI_PATH:-claude} + - NODE_ENV=production + volumes: + # Data persistence + - claudecodeui-data:/data + - claudecodeui-config:/config + # Mount user's .claude directory for project access (read-only) + - ${HOME}/.claude:/home/node/.claude:ro + # Mount user's home for project access (optional, configure as needed) + # Uncomment and adjust if you need access to specific project directories + # - ${HOME}/projects:/projects:rw + networks: + - claudecodeui + - public + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + labels: + - "traefik.enable=true" + - "traefik.http.routers.claudecodeui.rule=Host(`claudecodeui.fhijazi.com`)" + - "traefik.http.routers.claudecodeui.entrypoints=web" + - "traefik.http.services.claudecodeui.loadbalancer.server.port=3001" + - "traefik.http.routers.claudecodeui.middlewares=common-headers@file" + # WebSocket support + - "traefik.http.routers.claudecodeui-ws.rule=Host(`claudecodeui.fhijazi.com`) && PathPrefix(`/ws`)" + - "traefik.http.routers.claudecodeui-ws.entrypoints=web" + - "traefik.http.services.claudecodeui-ws.loadbalancer.server.port=3001" + +volumes: + claudecodeui-data: + name: claudecodeui_data + claudecodeui-config: + name: claudecodeui_config From 556e6c9201b03bd0e6f7d81f24c303e6b42c2ae6 Mon Sep 17 00:00:00 2001 From: Faris Hijazi Date: Tue, 25 Nov 2025 00:45:01 +0300 Subject: [PATCH 2/2] fix: update compose, removed traefik from it --- .dockerignore | 6 +++--- Dockerfile | 4 ---- docker-compose.yml | 26 ++++++++------------------ 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/.dockerignore b/.dockerignore index 0deaf533..43f893aa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,6 +41,6 @@ Thumbs.db .travis.yml # Docker -Dockerfile* -docker-compose* -.dockerignore +#Dockerfile* +#docker-compose* +#.dockerignore diff --git a/Dockerfile b/Dockerfile index 373e1332..9509f83c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,9 +64,5 @@ ENV NODE_ENV=production \ # Expose port EXPOSE 3001 -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3001/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" - # Start server CMD ["node", "server/index.js"] diff --git a/docker-compose.yml b/docker-compose.yml index e555dd0e..5e7d7597 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,11 +6,10 @@ networks: services: claudecodeui: - container_name: claudecodeui build: context: . dockerfile: Dockerfile - image: claudecodeui:latest + # image: claudecodeui:latest restart: unless-stopped ports: - "${EXTERNAL_PORT:-3002}:3001" @@ -24,14 +23,14 @@ services: - CLAUDE_CLI_PATH=${CLAUDE_CLI_PATH:-claude} - NODE_ENV=production volumes: - # Data persistence + # sadly these paths must be hardcoded (no we cannot use $HOME) because claude code uses + # hardcoded absolute paths in the ../node/.claude file and Docker must have identical full ones + #- /home/faris/Projects/:/home/faris/Projects/ + - ${HOME}/Projects/:${HOME}/Projects/ +# - /mnt/d2/Projects/:/mnt/d2/Projects/ + - ${HOME}/.claude:/home/node/.claude:rw - claudecodeui-data:/data - claudecodeui-config:/config - # Mount user's .claude directory for project access (read-only) - - ${HOME}/.claude:/home/node/.claude:ro - # Mount user's home for project access (optional, configure as needed) - # Uncomment and adjust if you need access to specific project directories - # - ${HOME}/projects:/projects:rw networks: - claudecodeui - public @@ -41,19 +40,10 @@ services: timeout: 10s retries: 3 start_period: 40s - labels: - - "traefik.enable=true" - - "traefik.http.routers.claudecodeui.rule=Host(`claudecodeui.fhijazi.com`)" - - "traefik.http.routers.claudecodeui.entrypoints=web" - - "traefik.http.services.claudecodeui.loadbalancer.server.port=3001" - - "traefik.http.routers.claudecodeui.middlewares=common-headers@file" - # WebSocket support - - "traefik.http.routers.claudecodeui-ws.rule=Host(`claudecodeui.fhijazi.com`) && PathPrefix(`/ws`)" - - "traefik.http.routers.claudecodeui-ws.entrypoints=web" - - "traefik.http.services.claudecodeui-ws.loadbalancer.server.port=3001" volumes: claudecodeui-data: name: claudecodeui_data claudecodeui-config: name: claudecodeui_config +