feat: add Docker support and GitHub Container Registry deployment#293
feat: add Docker support and GitHub Container Registry deployment#293NickBolles wants to merge 6 commits intositeboon:mainfrom
Conversation
- Add multi-stage Dockerfile for production builds - Add docker-compose.yml for local development and orchestration - Add .dockerignore to optimize build context - Add GitHub Action workflow to automatically build and push Docker images to GHCR - Support for multi-platform builds (linux/amd64, linux/arm64) - Include health checks and proper security practices (non-root user)
- Install @anthropic-ai/claude-code globally in Docker image
- Change .claude volume from host mount to named volume for portability
- Fix GitHub Action tag format error (change prefix from {{branch}}- to sha-)
- Set CLAUDE_CLI_PATH environment variable to 'claude'
- Update volume comments for clarity
This makes the container fully self-contained and portable without requiring
Claude CLI to be installed on the host system.
- Add three deployment options: GHCR pull, build from source, docker-compose - Document environment variables and their defaults - Explain volume configuration for data persistence - Include health check monitoring instructions - Add container management commands - Provide troubleshooting guide for common Docker issues - Note that Claude CLI is pre-installed in the container
- Add Unraid template XML with full container configuration - Include comprehensive Unraid-specific documentation - Configure PUID/PGID support for Unraid permissions - Set up appdata paths following Unraid conventions - Add multiple project directory mapping options - Include WebUI configuration and health checks - Update main README with Unraid deployment section Template ready for Community Applications submission. Users can install via CA or manually using the template URL: https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml
Add comprehensive Docker initialization system with taskmaster CLI support: - Install taskmaster-cli alongside Claude Code, Cursor, and Codex CLIs - Create docker-entrypoint.sh to run custom init scripts on container startup - Add init-scripts directory with example configurations for: - Claude Code CLI settings - Cursor IDE preferences - Taskmaster configuration - Git user setup - Additional tool installation - Update docker-compose.yml with new volumes for cursor, taskmaster, and init-scripts - Add comprehensive documentation in README.md and init-scripts/README.md - Include .gitignore to protect custom scripts while keeping examples This enables users to customize their Docker environment on startup without rebuilding the container.
WalkthroughThis pull request introduces comprehensive Docker containerization support to the project, adding multi-stage Dockerfile builds, GitHub Actions CI/CD workflows for automated image publishing to container registries, Docker Compose orchestration, container initialization scripts, and Unraid deployment templates with documentation. Changes
Sequence DiagramssequenceDiagram
actor Dev as Developer
participant GA as GitHub Actions
participant Builder as Docker Builder
participant Registry as GHCR Registry
participant Attestor as Build Attestation
Dev->>GA: Push to main or tag vX.Y.Z
GA->>Builder: Trigger build (linux/amd64, linux/arm64)
Builder->>Builder: Multi-stage build & test
Builder->>Registry: Push image with metadata tags
Registry-->>GA: Push successful
GA->>Attestor: Generate provenance
Attestor->>Registry: Attest build provenance
Registry-->>GA: Attestation complete
GA-->>Dev: Workflow success
sequenceDiagram
participant Container as Docker Container
participant Entrypoint as docker-entrypoint.sh
participant InitScripts as Init Scripts (/init-scripts)
participant Server as Node.js Server
Container->>Entrypoint: Container startup
Entrypoint->>InitScripts: Scan for executable .sh files
loop For each script (sorted order)
InitScripts->>InitScripts: Execute script
InitScripts-->>Entrypoint: Log result (success/warning)
end
Entrypoint->>Server: exec node server/index.js
Server-->>Container: Server running
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In @.github/workflows/docker-publish.yml:
- Around line 57-67: The Dockerfile build step references a non-existent npm
package "taskmaster-cli" and installs the Cursor CLI via "curl -fsSL
https://cursor.com/install | bash" which can fail on ARM64 Alpine due to missing
runtime libs; remove or replace the "taskmaster-cli" npm install line (ensure
only valid packages remain, e.g., keep @anthropic-ai/claude-code and
@openai/codex), and for Cursor on ARM64 either install required system libraries
(glibc, libstdc++/libgcc equivalents) into the image after the Cursor install or
switch the base image used for that stage to a distro with broader runtime
support (e.g., debian/ubuntu) to ensure the linux/arm64 platform build succeeds.
In @docker-entrypoint.sh:
- Around line 1-37: The init-scripts existence check is brittle because it uses
command substitution with ls under set -e; change the detection to use a safe
glob check such as compgen -G "/init-scripts/*.sh" >/dev/null || true (or enable
nullglob) instead of $(ls ...), and update the chmod hint to print the script's
full path (e.g., /init-scripts/$(basename "$script")) when suggesting chmod;
modify the loop around /init-scripts/*.sh in docker-entrypoint.sh accordingly
and keep the same executable/non-executable branches and messages.
In @Dockerfile:
- Around line 31-39: The RUN layer in the Dockerfile installs build-time
packages (python3, make, g++) into the production image; remove these from the
production-stage RUN invocation or move them into the build-stage only so the
final image doesn't contain unnecessary build tools, retaining only runtime
packages (git, ca-certificates, tzdata, bash, curl) and add a brief comment next
to the RUN line explaining any exceptions if a CLI truly needs one of those
tools at runtime; if any package is required at runtime, explicitly document why
and keep only that package.
- Around line 54-68: Remove the nonexistent "taskmaster-cli" install line, pin
the npm packages to their known versions by installing
@anthropic-ai/claude-code@2.1.4 and @openai/codex@0.80.0, replace the insecure
curl | bash Cursor install with a verified method (use an upstream-provided
stable release/tarball with checksum or documented installer that supports
integrity verification) or clearly document the accepted risk, and ensure the
Dockerfile fails fast on errors by enabling shell error checks (e.g., set
-e/exit on failure) around each CLI installation step.
In @README.md:
- Around line 387-390: The fenced code block showing the raw URL lacks a
language tag and triggers markdownlint MD040; update the triple-backtick fence
for that block (the fenced block containing
"https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml")
to include a language identifier such as "text" (i.e., replace ``` with ```text)
so the block is annotated and MD040 is satisfied.
- Around line 127-151: The README references the wrong GHCR image namespace;
replace both occurrences of "ghcr.io/nickbolles/claudecodeui" in the Docker
example (the docker pull and the image name in the docker run command) with the
actual publish target "ghcr.io/siteboon/claudecodeui" so the documented pull/run
commands match the workflow's "${{ github.repository }}" publish location.
In @unraid/claudecodeui.xml:
- Around line 4-11: The Repository and Registry fields reference
ghcr.io/nickbolles/claudecodeui while the Support and Project URLs (Support and
Project) use siteboon/claudecodeui; update the Repository element value from
"ghcr.io/nickbolles/claudecodeui:latest" to
"ghcr.io/siteboon/claudecodeui:latest" and the Registry element from
"https://ghcr.io/nickbolles/claudecodeui" to
"https://ghcr.io/siteboon/claudecodeui" so the image namespace matches the
project/support URLs (adjust the Repository and Registry elements in
claudecodeui.xml accordingly).
🧹 Nitpick comments (13)
.dockerignore (2)
8-9: Remove duplicate.github/exclusion.Line 9 and line 39 both exclude
.github/. Remove one to reduce redundancy.Also applies to: 38-39
44-46: Clarify intent of commented Docker file exclusions.Lines 44–46 comment out Dockerfile*, docker-compose*, and .dockerignore exclusions. Standard practice excludes these from the build context or leaves them unexcluded if they're genuinely needed at build time.
Clarify: Are these files intended to be packaged into the image? If not, uncomment them. If yes, document why they're needed in the image runtime.
unraid/README.md (3)
26-29: Add language identifier to fenced code block.Per markdown best practices (MD040), specify a language for fenced code blocks. This example shows a repository URL, so use
`url`or`text`.Proposed fix
Add this repository URL: - ``` + ```url https://github.com/siteboon/claudecodeui</details> --- `40-44`: **Add language identifier to fenced code block.** Per markdown best practices (MD040), specify a language for this code block showing a template URL. Use `` `url` `` or `` `text` ``. <details> <summary>Proposed fix</summary> ```diff Set **Template URL** to: - ``` + ```url https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml</details> --- `155-159`: **Optional grammar refinement: use "back up" (two words) as a verb.** Line 155 uses "To backup:" which is colloquially acceptable but formal style prefers "back up" (two words) for the verb form. This is a minor style suggestion and not critical. </blockquote></details> <details> <summary>init-scripts/04-example-install-tools.sh (1)</summary><blockquote> `1-39`: **Avoid misleading “installing…” log + prefer quoted heredoc in the example.** Since everything is commented out, Line 5 reads like work is happening when it isn’t; consider changing it to “Example: install additional tools (disabled)” or gate it behind an env flag. Also, if you keep the `.bash_aliases` heredoc example (Line 27), `<<'EOF'` is safer to prevent accidental expansions when users uncomment. </blockquote></details> <details> <summary>init-scripts/03-example-git-config.sh (1)</summary><blockquote> `1-31`: **Optional: add a quick `git` presence check for when users uncomment.** If someone enables the commented `git config` block, it’ll fail noisily if `git` isn’t present in the container; a tiny guard like `command -v git >/dev/null || { echo "..."; exit 0; }` would make this more self-explanatory. </blockquote></details> <details> <summary>init-scripts/01-example-cursor-config.sh (1)</summary><blockquote> `10-25`: **Use a quoted heredoc + consider making the model value an obvious placeholder.** If users uncomment the example, switching to `<<'EOF'` avoids accidental expansions, and `"cursor.ai.model"` could be `"YOUR_MODEL_HERE"` to reduce staleness. </blockquote></details> <details> <summary>docker-compose.yml (3)</summary><blockquote> `12-12`: **Remove commented-out configuration line.** The commented `image` line appears to be leftover code. Since the service uses `build`, this line should be removed for clarity. <details> <summary>♻️ Proposed fix</summary> ```diff build: context: . dockerfile: Dockerfile - # image: claudecodeui:latest restart: unless-stopped
26-28: Document or provide example for project directory mount.The volume mount
${HOME}/Projects/:${HOME}/Projects/assumes this directory exists on the host. This path is user-specific and may not exist for all users. Consider adding a comment indicating this should be customized or making it optional with an example in documentation.♻️ Suggested improvement
# Mount project directories for code access - # Adjust these paths to match your local project locations - - ${HOME}/Projects/:${HOME}/Projects/ + # Uncomment and adjust these paths to match your local project locations + # - ${HOME}/Projects/:${HOME}/Projects/ + # - /path/to/your/projects:/workspace/projects
41-46: Simplify the healthcheck command using curl.The inline Node.js code is functional but more complex than necessary. Since
curlis available in the container, use a simpler approach:♻️ Simplified healthcheck
healthcheck: - test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s.github/workflows/docker-publish.yml (1)
69-75: Fix minor syntax issue in IMAGE_NAME reference.Line 73 has an extra closing brace:
${{ env.IMAGE_NAME}}should be${{ env.IMAGE_NAME }}(with space before closing braces) for consistency, though it should still function.📝 Proposed fix
- subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}Dockerfile (1)
87-91: Consider adding HEALTHCHECK instruction.While the docker-compose.yml defines a health check, adding a
HEALTHCHECKinstruction in the Dockerfile would make the image self-contained and improve container orchestration in environments beyond Docker Compose.💚 Proposed health check addition
# Expose port EXPOSE 3001 +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1 + # Start server using entrypoint script CMD ["/app/docker-entrypoint.sh"]Note: This assumes
wgetis available. If not, you could usecurl(already installed in the image):HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3001/api/health || exit 1
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
.dockerignore.github/workflows/docker-publish.ymlDockerfileREADME.mddocker-compose.ymldocker-entrypoint.shinit-scripts/.gitignoreinit-scripts/00-example-claude-config.shinit-scripts/01-example-cursor-config.shinit-scripts/02-example-taskmaster-config.shinit-scripts/03-example-git-config.shinit-scripts/04-example-install-tools.shinit-scripts/README.mdunraid/README.mdunraid/claudecodeui.xml
🧰 Additional context used
🪛 LanguageTool
unraid/README.md
[grammar] ~157-~157: Ensure spelling is correct
Context: ...eui/. To backup: 1. Stop the container 2. Backup the entire /mnt/user/appdata/claudecod...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
unraid/README.md
27-27: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
41-41: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
README.md
387-387: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (13)
init-scripts/.gitignore (1)
1-8: Clean and purposeful gitignore pattern.The negation pattern allows example scripts to be tracked while keeping user-provided scripts out of version control. This aligns well with the initialization script architecture.
unraid/README.md (1)
1-181: Comprehensive and well-structured documentation.The Unraid README provides clear installation options, configuration tables, troubleshooting guidance, and operational procedures. The organization is logical and addresses the needs of Unraid users effectively.
init-scripts/02-example-taskmaster-config.sh (1)
1-32: LGTM! Example script follows consistent pattern.The script structure is appropriate, with all configuration commented out by default. This allows users to review and customize before enabling.
init-scripts/00-example-claude-config.sh (1)
1-29: LGTM! Consistent example script structure.The script follows the same safe pattern as other example scripts, with configuration disabled by default.
unraid/claudecodeui.xml (1)
35-48: Configuration options look appropriate.The Config definitions provide sensible defaults and appropriate descriptions for Unraid deployment. The PUID/PGID defaults (99/100) are correct for Unraid's nobody/users.
init-scripts/README.md (1)
1-214: Excellent documentation!The init-scripts documentation is comprehensive, well-structured, and provides clear guidance for users. It covers all essential topics: usage, best practices, troubleshooting, and security considerations. The examples are practical and demonstrate good practices like idempotency.
.github/workflows/docker-publish.yml (3)
3-14: LGTM! Comprehensive trigger configuration.The workflow triggers cover the essential scenarios: pushes to main/master branches, version tags, pull requests, and manual dispatch. This provides good flexibility for CI/CD operations.
23-26: LGTM! Appropriate permissions for GHCR publishing.The permissions are correctly scoped:
contents: readfor repository accesspackages: writefor pushing to GitHub Container Registryid-token: writefor attestation generation
43-55: LGTM! Comprehensive tagging strategy.The metadata extraction configuration generates appropriate tags for different scenarios:
- Branch names for development builds
- PR numbers for pull request builds
- Semantic versioning variants for releases
- SHA prefixes for traceability
latestfor default branchDockerfile (4)
1-23: LGTM! Well-structured builder stage.The builder stage correctly:
- Uses node:20-alpine as base
- Installs necessary build dependencies for node-gyp
- Uses
npm cifor reproducible builds- Cleans npm cache to reduce layer size
- Builds the frontend assets
70-72: Verify necessity of CLI configuration directories.The Dockerfile creates configuration directories for all four CLIs (
.claude,.cursor,.openai,.taskmaster), but this assumes:
- All CLIs will install successfully
- All CLIs use these specific directory conventions
If any CLI installation fails (especially given the verification concerns), these directories may be unnecessary.
Consider:
- Creating directories only after successful CLI installation
- Letting CLIs create their own config directories on first run
- Removing unused directories if CLIs don't install
74-76: LGTM! Proper entrypoint script setup.The entrypoint script is correctly copied with proper ownership and made executable before switching to the non-root user.
78-85: LGTM! Security best practice: non-root user.Switching to the
nodeuser (uid/gid 1000) follows container security best practices by avoiding running the application as root.Environment variables are appropriately configured for the production runtime.
| - name: Build and push Docker image | ||
| id: build-and-push | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: . | ||
| push: ${{ github.event_name != 'pull_request' }} | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| platforms: linux/amd64,linux/arm64 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -type f -name "Dockerfile"Repository: siteboon/claudecodeui
Length of output: 235
🏁 Script executed:
fd --type f DockerfileRepository: siteboon/claudecodeui
Length of output: 75
🏁 Script executed:
cat -n DockerfileRepository: siteboon/claudecodeui
Length of output: 3046
🏁 Script executed:
npm view @anthropic-ai/claude-code 2>&1 | head -50Repository: siteboon/claudecodeui
Length of output: 1281
🏁 Script executed:
npm view @openai/codex 2>&1Repository: siteboon/claudecodeui
Length of output: 1269
🏁 Script executed:
npm view taskmaster-cli 2>&1Repository: siteboon/claudecodeui
Length of output: 477
🏁 Script executed:
npm view @anthropic-ai/claude-code cpu os 2>&1Repository: siteboon/claudecodeui
Length of output: 47
🏁 Script executed:
npm view @anthropic-ai/claude-code --json 2>&1 | jq '.cpu, .os, .engines' 2>&1 | head -20Repository: siteboon/claudecodeui
Length of output: 99
🏁 Script executed:
npm view @openai/codex --json 2>&1 | jq '.cpu, .os, .engines' 2>&1 | head -20Repository: siteboon/claudecodeui
Length of output: 95
🌐 Web query:
Cursor CLI installation ARM64 arm64 support Linux
💡 Result:
Short answer: Cursor provides Linux ARM64 builds (AppImage, .deb, .rpm) and the official install script detects arm64 — but ARM Linux (especially Raspberry Pi) can still hit runtime issues (missing system libraries / dynamic loader). See below.
Quick install (recommended):
- Run the official installer: curl https://cursor.com/install -fsS | bash. Verify with: cursor-agent --version. [1][2]
If you prefer manual packages:
- Download the Linux ARM64 package from Cursor’s downloads page (AppImage, .deb or .rpm) and install the .deb/.rpm for your distro. [2]
Common ARM64 problems and fixes
- Symptom: bundled node binary shows “cannot execute: required file not found” or trace trap on ARM boards (Raspberry Pi). This usually means the binary’s dynamic loader or shared libraries aren’t present on your system. Users have reported this. [3][4]
- Quick troubleshooting steps:
- Confirm architecture: uname -m (should be aarch64 / arm64).
- Run ldd on the bundled node to find missing libs:
ldd ~/.local/share/cursor-agent/versions//node - Install missing runtime packages (examples): apt install libc6 libstdc++6 libgcrypt20 libssl1.1 (or the distro equivalents) and ensure /lib/ld-linux-aarch64.so.1 exists. Reboot if needed. [3]
- If AppImage crashes, try the distro .deb/.rpm ARM64 package instead. [2][4]
If you still can’t run it, capture the error and the ldd output and open a support/bug thread (Cursor forum or GitHub) — include uname -a, distro and kernel version and the ldd results. [3][10]
Sources: Cursor install docs, downloads page, and community reports on ARM64 issues. [1][2][3][4]
Fix non-existent npm package and verify Cursor CLI ARM64 compatibility.
The Dockerfile will fail to build because taskmaster-cli does not exist in the npm registry (404 Not Found). Additionally, the Cursor CLI installation via curl -fsSL https://cursor.com/install | bash targets ARM64, but Alpine Linux's minimal image may lack runtime dependencies required by Cursor's bundled binaries (e.g., dynamic loader, libc6, libstdc++6).
Required actions:
- Remove the non-existent
taskmaster-cliinstallation from line 67 or replace with an existing package. - For Cursor CLI on ARM64 Alpine: Either install additional system libraries (libc6, libstdc++6, etc.) after the Cursor installation, or use a different base image with broader runtime support.
- The npm packages
@anthropic-ai/claude-codeand@openai/codexhave no platform restrictions and should install correctly across architectures.
🤖 Prompt for AI Agents
In @.github/workflows/docker-publish.yml around lines 57 - 67, The Dockerfile
build step references a non-existent npm package "taskmaster-cli" and installs
the Cursor CLI via "curl -fsSL https://cursor.com/install | bash" which can fail
on ARM64 Alpine due to missing runtime libs; remove or replace the
"taskmaster-cli" npm install line (ensure only valid packages remain, e.g., keep
@anthropic-ai/claude-code and @openai/codex), and for Cursor on ARM64 either
install required system libraries (glibc, libstdc++/libgcc equivalents) into the
image after the Cursor install or switch the base image used for that stage to a
distro with broader runtime support (e.g., debian/ubuntu) to ensure the
linux/arm64 platform build succeeds.
| public: | ||
| external: true |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the docker-compose.yml file to understand the full context
cat -n docker-compose.yml | head -20Repository: siteboon/claudecodeui
Length of output: 582
🏁 Script executed:
# Search for documentation about the 'public' network requirement
rg -n "public.*network" --type=md -C3Repository: siteboon/claudecodeui
Length of output: 47
🏁 Script executed:
# Also search for setup, installation, or README documentation
fd -e md -e rst -e txt | head -20Repository: siteboon/claudecodeui
Length of output: 169
🏁 Script executed:
# Check if there are any scripts related to network setup
fd -name "*setup*" -o -name "*install*" -o -name "*init*"Repository: siteboon/claudecodeui
Length of output: 295
🏁 Script executed:
# Search more broadly for references to the 'public' network
rg -n "public" --type=yaml -C2Repository: siteboon/claudecodeui
Length of output: 564
🏁 Script executed:
# Check main README for docker-compose or network setup instructions
cat README.mdRepository: siteboon/claudecodeui
Length of output: 21394
🏁 Script executed:
# Check init-scripts README
cat init-scripts/README.mdRepository: siteboon/claudecodeui
Length of output: 5541
Document the external network requirement in Docker Compose setup.
The docker-compose.yml references an external network public (line 5), but the README's Docker Compose section (lines provided for quick-start) does not document that this network must be created before running docker-compose up -d. Users following the current instructions will encounter a failure. Add a prerequisite step to either: (1) create the network with docker network create public, or (2) remove the external: true flag to have Docker Compose manage the network automatically.
| #!/bin/bash | ||
| set -e | ||
|
|
||
| echo "🚀 Claude Code UI - Container Initialization" | ||
| echo "===========================================" | ||
|
|
||
| # Run initialization scripts if they exist | ||
| if [ -d "/init-scripts" ] && [ "$(ls -A /init-scripts/*.sh 2>/dev/null)" ]; then | ||
| echo "" | ||
| echo "📝 Running initialization scripts..." | ||
| echo "" | ||
|
|
||
| # Sort and execute all .sh files in /init-scripts | ||
| for script in /init-scripts/*.sh; do | ||
| if [ -f "$script" ] && [ -x "$script" ]; then | ||
| echo "▶️ Executing: $(basename "$script")" | ||
| if bash "$script"; then | ||
| echo " ✅ Success: $(basename "$script")" | ||
| else | ||
| echo " ⚠️ Warning: $(basename "$script") failed with exit code $?" | ||
| echo " Continuing anyway..." | ||
| fi | ||
| echo "" | ||
| elif [ -f "$script" ]; then | ||
| echo "⚠️ Skipping non-executable script: $(basename "$script")" | ||
| echo " Run: chmod +x $(basename "$script") to enable" | ||
| echo "" | ||
| fi | ||
| done | ||
|
|
||
| echo "✅ Initialization scripts completed" | ||
| echo "" | ||
| else | ||
| echo "ℹ️ No initialization scripts found in /init-scripts" | ||
| echo " Create .sh files in /init-scripts to customize your environment" | ||
| echo "" | ||
| fi |
There was a problem hiding this comment.
Harden init-scripts detection (avoid ls in command substitution under set -e).
The Line 8 check relies on $(ls ...); with set -e, this pattern can be brittle. Prefer compgen -G (or nullglob) and print a full path in the chmod hint.
Proposed patch
#!/bin/bash
set -e
echo "🚀 Claude Code UI - Container Initialization"
echo "==========================================="
# Run initialization scripts if they exist
-if [ -d "/init-scripts" ] && [ "$(ls -A /init-scripts/*.sh 2>/dev/null)" ]; then
+if [ -d "/init-scripts" ] && compgen -G "/init-scripts/*.sh" > /dev/null; then
echo ""
echo "📝 Running initialization scripts..."
echo ""
# Sort and execute all .sh files in /init-scripts
for script in /init-scripts/*.sh; do
if [ -f "$script" ] && [ -x "$script" ]; then
echo "▶️ Executing: $(basename "$script")"
- if bash "$script"; then
+ if "$script"; then
echo " ✅ Success: $(basename "$script")"
else
- echo " ⚠️ Warning: $(basename "$script") failed with exit code $?"
+ rc=$?
+ echo " ⚠️ Warning: $(basename "$script") failed with exit code $rc"
echo " Continuing anyway..."
fi
echo ""
elif [ -f "$script" ]; then
echo "⚠️ Skipping non-executable script: $(basename "$script")"
- echo " Run: chmod +x $(basename "$script") to enable"
+ echo " Run: chmod +x $script to enable"
echo ""
fi
done📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #!/bin/bash | |
| set -e | |
| echo "🚀 Claude Code UI - Container Initialization" | |
| echo "===========================================" | |
| # Run initialization scripts if they exist | |
| if [ -d "/init-scripts" ] && [ "$(ls -A /init-scripts/*.sh 2>/dev/null)" ]; then | |
| echo "" | |
| echo "📝 Running initialization scripts..." | |
| echo "" | |
| # Sort and execute all .sh files in /init-scripts | |
| for script in /init-scripts/*.sh; do | |
| if [ -f "$script" ] && [ -x "$script" ]; then | |
| echo "▶️ Executing: $(basename "$script")" | |
| if bash "$script"; then | |
| echo " ✅ Success: $(basename "$script")" | |
| else | |
| echo " ⚠️ Warning: $(basename "$script") failed with exit code $?" | |
| echo " Continuing anyway..." | |
| fi | |
| echo "" | |
| elif [ -f "$script" ]; then | |
| echo "⚠️ Skipping non-executable script: $(basename "$script")" | |
| echo " Run: chmod +x $(basename "$script") to enable" | |
| echo "" | |
| fi | |
| done | |
| echo "✅ Initialization scripts completed" | |
| echo "" | |
| else | |
| echo "ℹ️ No initialization scripts found in /init-scripts" | |
| echo " Create .sh files in /init-scripts to customize your environment" | |
| echo "" | |
| fi | |
| #!/bin/bash | |
| set -e | |
| echo "🚀 Claude Code UI - Container Initialization" | |
| echo "===========================================" | |
| # Run initialization scripts if they exist | |
| if [ -d "/init-scripts" ] && compgen -G "/init-scripts/*.sh" > /dev/null; then | |
| echo "" | |
| echo "📝 Running initialization scripts..." | |
| echo "" | |
| # Sort and execute all .sh files in /init-scripts | |
| for script in /init-scripts/*.sh; do | |
| if [ -f "$script" ] && [ -x "$script" ]; then | |
| echo "▶️ Executing: $(basename "$script")" | |
| if "$script"; then | |
| echo " ✅ Success: $(basename "$script")" | |
| else | |
| rc=$? | |
| echo " ⚠️ Warning: $(basename "$script") failed with exit code $rc" | |
| echo " Continuing anyway..." | |
| fi | |
| echo "" | |
| elif [ -f "$script" ]; then | |
| echo "⚠️ Skipping non-executable script: $(basename "$script")" | |
| echo " Run: chmod +x $script to enable" | |
| echo "" | |
| fi | |
| done | |
| echo "✅ Initialization scripts completed" | |
| echo "" | |
| else | |
| echo "ℹ️ No initialization scripts found in /init-scripts" | |
| echo " Create .sh files in /init-scripts to customize your environment" | |
| echo "" | |
| fi |
🤖 Prompt for AI Agents
In @docker-entrypoint.sh around lines 1 - 37, The init-scripts existence check
is brittle because it uses command substitution with ls under set -e; change the
detection to use a safe glob check such as compgen -G "/init-scripts/*.sh"
>/dev/null || true (or enable nullglob) instead of $(ls ...), and update the
chmod hint to print the script's full path (e.g., /init-scripts/$(basename
"$script")) when suggesting chmod; modify the loop around /init-scripts/*.sh in
docker-entrypoint.sh accordingly and keep the same executable/non-executable
branches and messages.
| RUN apk add --no-cache \ | ||
| python3 \ | ||
| make \ | ||
| g++ \ | ||
| git \ | ||
| ca-certificates \ | ||
| tzdata \ | ||
| bash \ | ||
| curl |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove build tools from production image.
The production stage includes build dependencies (python3, make, g++) that are typically only needed during build time. Including these tools:
- Significantly increases image size
- Expands the attack surface
- Violates container minimalism best practices
If these tools are truly required at runtime for the CLIs, document why. Otherwise, remove them to create a leaner, more secure image.
♻️ Proposed fix to remove build tools
# Install runtime dependencies including bash and curl for CLI installations
RUN apk add --no-cache \
- python3 \
- make \
- g++ \
- git \
ca-certificates \
tzdata \
bash \
curlNote: If any of the CLIs genuinely require these build tools at runtime (not just for installation), keep only those specific dependencies and add a comment explaining why.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| RUN apk add --no-cache \ | |
| python3 \ | |
| make \ | |
| g++ \ | |
| git \ | |
| ca-certificates \ | |
| tzdata \ | |
| bash \ | |
| curl | |
| RUN apk add --no-cache \ | |
| ca-certificates \ | |
| tzdata \ | |
| bash \ | |
| curl |
🤖 Prompt for AI Agents
In @Dockerfile around lines 31 - 39, The RUN layer in the Dockerfile installs
build-time packages (python3, make, g++) into the production image; remove these
from the production-stage RUN invocation or move them into the build-stage only
so the final image doesn't contain unnecessary build tools, retaining only
runtime packages (git, ca-certificates, tzdata, bash, curl) and add a brief
comment next to the RUN line explaining any exceptions if a CLI truly needs one
of those tools at runtime; if any package is required at runtime, explicitly
document why and keep only that package.
| # Install all supported CLIs globally | ||
| # 1. Claude Code CLI - Anthropic's official CLI | ||
| RUN npm install -g @anthropic-ai/claude-code && \ | ||
| npm cache clean --force | ||
|
|
||
| # 2. Cursor CLI - Install via official method | ||
| RUN curl -fsSL https://cursor.com/install | bash | ||
|
|
||
| # 3. Codex CLI - OpenAI's Codex CLI | ||
| RUN npm install -g @openai/codex && \ | ||
| npm cache clean --force | ||
|
|
||
| # 4. Taskmaster CLI - AI task orchestration | ||
| RUN npm install -g taskmaster-cli && \ | ||
| npm cache clean --force |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if npm packages exist in the registry
echo "=== Checking @anthropic-ai/claude-code ==="
npm view @anthropic-ai/claude-code 2>&1 | head -20
echo -e "\n=== Checking @openai/codex ==="
npm view @openai/codex 2>&1 | head -20
echo -e "\n=== Checking taskmaster-cli ==="
npm view taskmaster-cli 2>&1 | head -20Repository: siteboon/claudecodeui
Length of output: 2277
🌐 Web query:
Cursor CLI official installation method documentation
💡 Result:
Official install method (macOS, Linux, Windows WSL):
- Run: curl https://cursor.com/install -fsS | bash. [1][2]
- Verify: cursor-agent --version and ensure the install path (e.g. ~/.local/bin) is on your PATH. [1][2]
Sources: Cursor documentation and site. [1] [2]
References
[1] Cursor docs — CLI / Installation.
[2] Cursor site — CLI (install command shown).
🌐 Web query:
Cursor code editor install script cursor.com/install
💡 Result:
You can install Cursor’s CLI/agent with the official one‑line installer:
curl https://cursor.com/install -fsS | bash
Afterwards verify with:
cursor-agent --version
(Installer works on macOS, Linux and Windows/WSL; see platform details and post‑install PATH steps in the docs). [1][2]
Sources:
[1] Cursor docs — CLI installation. [2] Cursor docs — Download/Installation guide.
Critical security and availability issues with CLI installation approach.
Line 60 and the overall CLI installation strategy have significant problems:
- taskmaster-cli does not exist in the npm registry (404 error). This line will fail during build.
- curl piping to bash is a security risk despite being Cursor's official method:
- Executes untrusted code without integrity verification
- Runs with elevated privileges before USER switch
- Breaks build reproducibility (external script can change at any time)
- Missing version pins:
@anthropic-ai/claude-code(exists, v2.1.4) and@openai/codex(exists, v0.80.0) should use explicit versions.
Recommendations:
- Remove
taskmaster-clientirely (package does not exist). - For Cursor CLI: Either find a more secure alternative installation method with verification, or accept the upstream risk and document it. If using
curl | bash, pin to a specific hash verification if Cursor provides one. - Pin versions for npm packages:
npm install -g @anthropic-ai/claude-code@2.1.4andnpm install -g @openai/codex@0.80.0. - Add error handling (
set -eor|| exit 1) to fail fast on any installation error.
🤖 Prompt for AI Agents
In @Dockerfile around lines 54 - 68, Remove the nonexistent "taskmaster-cli"
install line, pin the npm packages to their known versions by installing
@anthropic-ai/claude-code@2.1.4 and @openai/codex@0.80.0, replace the insecure
curl | bash Cursor install with a verified method (use an upstream-provided
stable release/tarball with checksum or documented installer that supports
integrity verification) or clearly document the accepted risk, and ensure the
Dockerfile fails fast on errors by enabling shell error checks (e.g., set
-e/exit on failure) around each CLI installation step.
| #### Option 1: Pull from GitHub Container Registry (Recommended) | ||
|
|
||
| Use the pre-built multi-platform image (supports amd64 and arm64): | ||
|
|
||
| ```bash | ||
| # Pull the latest image | ||
| docker pull ghcr.io/nickbolles/claudecodeui:latest | ||
|
|
||
| # Run the container | ||
| docker run -d \ | ||
| --name claudecodeui \ | ||
| -p 3002:3001 \ | ||
| -v claudecodeui-data:/data \ | ||
| -v claudecodeui-config:/config \ | ||
| -v claudecodeui-claude:/home/node/.claude \ | ||
| -v claudecodeui-cursor:/home/node/.cursor \ | ||
| -v claudecodeui-taskmaster:/home/node/.taskmaster \ | ||
| -v ./init-scripts:/init-scripts \ | ||
| -v ~/Projects:~/Projects \ | ||
| -e PORT=3001 \ | ||
| -e DATABASE_PATH=/data/auth.db \ | ||
| -e CONTEXT_WINDOW=160000 \ | ||
| --restart unless-stopped \ | ||
| ghcr.io/nickbolles/claudecodeui:latest | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) What image does the workflow build/push?
echo "=== GitHub Workflows ==="
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" | while read f; do
echo "--- $f ---"
cat "$f"
done
# 2) What image does the README advertise?
echo ""
echo "=== README Image References ==="
rg -n "ghcr\.io/" README.md || echo "No GHCR references found"
# 3) Check for any image configuration in package.json or other config files
echo ""
echo "=== Other Image References ==="
rg -n "claudecodeui" . -t json -t yml -t yaml -t toml --max-count 20 || trueRepository: siteboon/claudecodeui
Length of output: 2399
Update image namespace in README to match actual GitHub Container Registry publish target.
The workflow publishes to ghcr.io/siteboon/claudecodeui (using ${{ github.repository }}), but the README at lines 133 and 150 instructs users to pull from ghcr.io/nickbolles/claudecodeui. This mismatch will cause deployment failures with pull/image-not-found errors. Update both references to use siteboon/claudecodeui.
🤖 Prompt for AI Agents
In @README.md around lines 127 - 151, The README references the wrong GHCR image
namespace; replace both occurrences of "ghcr.io/nickbolles/claudecodeui" in the
Docker example (the docker pull and the image name in the docker run command)
with the actual publish target "ghcr.io/siteboon/claudecodeui" so the documented
pull/run commands match the workflow's "${{ github.repository }}" publish
location.
| ``` | ||
| https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml | ||
| ``` | ||
| 3. Click **Apply** |
There was a problem hiding this comment.
Fix markdownlint MD040 (add a language to the fenced block).
Proposed patch
- ```
+ ```text
https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml
```📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml | |
| ``` | |
| 3. Click **Apply** |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
387-387: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In @README.md around lines 387 - 390, The fenced code block showing the raw URL
lacks a language tag and triggers markdownlint MD040; update the triple-backtick
fence for that block (the fenced block containing
"https://raw.githubusercontent.com/siteboon/claudecodeui/main/unraid/claudecodeui.xml")
to include a language identifier such as "text" (i.e., replace ``` with ```text)
so the block is annotated and MD040 is satisfied.
| <Repository>ghcr.io/nickbolles/claudecodeui:latest</Repository> | ||
| <Registry>https://ghcr.io/nickbolles/claudecodeui</Registry> | ||
| <Network>bridge</Network> | ||
| <MyIP/> | ||
| <Shell>sh</Shell> | ||
| <Privileged>false</Privileged> | ||
| <Support>https://github.com/siteboon/claudecodeui/issues</Support> | ||
| <Project>https://github.com/siteboon/claudecodeui</Project> |
There was a problem hiding this comment.
Repository namespace mismatch between image and project.
The container image references ghcr.io/nickbolles/claudecodeui (lines 4-5), but the Support and Project URLs point to siteboon/claudecodeui (lines 10-11). Since this PR is for the siteboon/claudecodeui repository, the image repository should likely be ghcr.io/siteboon/claudecodeui for consistency.
🔧 Proposed fix
- <Repository>ghcr.io/nickbolles/claudecodeui:latest</Repository>
- <Registry>https://ghcr.io/nickbolles/claudecodeui</Registry>
+ <Repository>ghcr.io/siteboon/claudecodeui:latest</Repository>
+ <Registry>https://ghcr.io/siteboon/claudecodeui</Registry>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Repository>ghcr.io/nickbolles/claudecodeui:latest</Repository> | |
| <Registry>https://ghcr.io/nickbolles/claudecodeui</Registry> | |
| <Network>bridge</Network> | |
| <MyIP/> | |
| <Shell>sh</Shell> | |
| <Privileged>false</Privileged> | |
| <Support>https://github.com/siteboon/claudecodeui/issues</Support> | |
| <Project>https://github.com/siteboon/claudecodeui</Project> | |
| <Repository>ghcr.io/siteboon/claudecodeui:latest</Repository> | |
| <Registry>https://ghcr.io/siteboon/claudecodeui</Registry> | |
| <Network>bridge</Network> | |
| <MyIP/> | |
| <Shell>sh</Shell> | |
| <Privileged>false</Privileged> | |
| <Support>https://github.com/siteboon/claudecodeui/issues</Support> | |
| <Project>https://github.com/siteboon/claudecodeui</Project> |
🤖 Prompt for AI Agents
In @unraid/claudecodeui.xml around lines 4 - 11, The Repository and Registry
fields reference ghcr.io/nickbolles/claudecodeui while the Support and Project
URLs (Support and Project) use siteboon/claudecodeui; update the Repository
element value from "ghcr.io/nickbolles/claudecodeui:latest" to
"ghcr.io/siteboon/claudecodeui:latest" and the Registry element from
"https://ghcr.io/nickbolles/claudecodeui" to
"https://ghcr.io/siteboon/claudecodeui" so the image namespace matches the
project/support URLs (adjust the Repository and Registry elements in
claudecodeui.xml accordingly).
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.