Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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

# Start server
CMD ["node", "server/index.js"]
49 changes: 49 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
networks:
claudecodeui:
name: claudecodeui
public:
external: true

services:
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:
# 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
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

volumes:
claudecodeui-data:
name: claudecodeui_data
claudecodeui-config:
name: claudecodeui_config