# ============================================================================ # Claude Code Agents UI - Dockerfile # Combines Ngxba/claude-code-agents-ui frontend with Claude Code CLI runtime # ============================================================================ # --- Build Stage --- FROM node:20-alpine AS builder WORKDIR /app # Install build dependencies RUN apk add --no-cache git # Clone the Claude Code Agents UI repository RUN git clone --depth 1 https://github.com/Ngxba/claude-code-agents-ui.git . # Install dependencies (prefer bun if lockfile present, fallback to npm) RUN if [ -f bun.lockb ]; then \ npm install -g bun && bun install --frozen-lockfile; \ else \ npm install; \ fi # Build the Nuxt application RUN npm run build 2>/dev/null || npx nuxt build # --- Production Stage --- FROM node:20-alpine WORKDIR /app # Install runtime dependencies RUN apk add --no-cache \ python3 \ py3-pip \ git \ curl \ wget \ bash \ vim \ nano \ build-base \ openssh-client \ ca-certificates \ tini \ jq # Install Claude Code CLI RUN npm install -g @anthropic-ai/claude-code # Install Python tools for agent execution RUN pip3 install --no-cache-dir --break-system-packages \ requests \ python-dotenv \ pydantic \ fastapi \ uvicorn # Copy built Nuxt application from builder COPY --from=builder /app/.output /app/.output COPY --from=builder /app/package.json /app/package.json # Create non-root user for security RUN addgroup -g 1000 claude && \ adduser -D -u 1000 -G claude claude # Create necessary directories RUN mkdir -p /workspace /root/.claude && \ chown -R claude:claude /workspace && \ chown -R root:root /root/.claude # Set app ownership RUN chown -R claude:claude /app # ---------------------------------------------------------------------------- # Entrypoint: bootstraps Claude auth then starts the UI # ---------------------------------------------------------------------------- # Auth strategy (in priority order): # 1. If ANTHROPIC_API_KEY is set → write a minimal credentials file so the # Claude Code CLI recognises the key without needing an interactive login. # 2. If a credentials file was volume-mounted at /root/.claude/.credentials.json # already (e.g. from a docker volume) → use it as-is. # 3. Fall through to the UI which will surface an auth error to the user. # ---------------------------------------------------------------------------- RUN cat > /entrypoint.sh << 'ENTRYPOINT_EOF' #!/bin/sh set -e CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-/root/.claude}" CREDS_FILE="${CLAUDE_DIR}/.credentials.json" echo "[entrypoint] Claude config dir: ${CLAUDE_DIR}" mkdir -p "${CLAUDE_DIR}" # --- Bootstrap auth from ANTHROPIC_API_KEY --- if [ -n "${ANTHROPIC_API_KEY}" ]; then if [ ! -f "${CREDS_FILE}" ]; then echo "[entrypoint] Writing credentials from ANTHROPIC_API_KEY..." # Claude Code CLI accepts a credentials file with an apiKey field as # an alternative to OAuth. This is the non-interactive equivalent of # running `claude auth login` with an API key. cat > "${CREDS_FILE}" << CREDS_EOF { "claudeAiOauth": null, "apiKey": "${ANTHROPIC_API_KEY}" } CREDS_EOF chmod 600 "${CREDS_FILE}" echo "[entrypoint] Credentials written successfully." else echo "[entrypoint] Credentials file already exists, skipping write." fi else echo "[entrypoint] WARNING: ANTHROPIC_API_KEY not set." echo "[entrypoint] Set it in your .env file or via 'docker run -e ANTHROPIC_API_KEY=...'." echo "[entrypoint] The UI will load but Claude Code execution will fail without auth." fi # --- Verify Claude CLI can see the key --- if claude auth status 2>/dev/null | grep -q "Logged in\|api key"; then echo "[entrypoint] Claude CLI auth: OK" else echo "[entrypoint] Claude CLI auth: not authenticated (will use ANTHROPIC_API_KEY at runtime)" fi # --- Start the Nuxt application --- echo "[entrypoint] Starting Claude Code Agents UI on port 3000..." exec node /app/.output/server/index.mjs ENTRYPOINT_EOF RUN chmod +x /entrypoint.sh # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:3000 || exit 1 EXPOSE 3000 # Use tini for proper signal handling ENTRYPOINT ["/sbin/tini", "--"] CMD ["/entrypoint.sh"]