Skip to content

fix(Docker): Add claude-devtools-config.json volume to Docker Compose#179

Open
sentisso wants to merge 1 commit intomatt1398:mainfrom
sentisso:main
Open

fix(Docker): Add claude-devtools-config.json volume to Docker Compose#179
sentisso wants to merge 1 commit intomatt1398:mainfrom
sentisso:main

Conversation

@sentisso
Copy link
Copy Markdown

@sentisso sentisso commented Apr 19, 2026

Fix for #178

When running claude-devtools in Docker, it's not possible to save the user config claude-devtools-config.json because of the read-only mount in docker-compose.yml:

[Service:ConfigManager] Error saving config: Error: EROFS: read-only file system, open '/data/.claude/claude-devtools-config.json'

This PR adds an exception read-write mount only on the claude-devtools config, so the user setting changes are persisted across container restarts

Summary by CodeRabbit

  • Chores
    • Updated container configuration to support mounting configuration files with read-write permissions into the container environment.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the docker-compose.yml file to include a read-write volume mount for the claude-devtools-config.json configuration file. Feedback indicates that mounting a specific file can cause Docker to create a directory if the file is missing on the host, potentially leading to application failures. It is suggested to ensure the file exists beforehand or to adjust the parent directory's mount permissions.

Comment thread docker-compose.yml
- "3456:3456"
volumes:
- ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro
- ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Mounting a specific file as a volume can be problematic if the file does not already exist on the host. If claude-devtools-config.json is missing from the host's .claude directory when docker compose up is run, Docker will create a directory with that name instead of a file. This will cause the application to fail with an EISDIR (Illegal operation on a directory) error when it attempts to read or write the configuration. It is recommended to ensure the file exists on the host before starting the container, or to consider mounting the entire ${CLAUDE_DIR:-~/.claude} directory as rw if the read-only restriction on session data is not strictly required for your use case.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Added a bind-mount volume configuration in docker-compose.yml for the claude-devtools-config.json file, mapping it from the host machine into the container with read-write permissions while preserving the existing read-only mount for the parent directory.

Changes

Cohort / File(s) Summary
Docker Compose Configuration
docker-compose.yml
Added bind-mount volume for claude-devtools-config.json with read-write permissions at /data/.claude/claude-devtools-config.json.

Possibly related issues

  • #178: Directly addresses the need to add read-write bind-mount for claude-devtools-config.json in docker-compose.yml to enable config saving.

Suggested labels

bug


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yml`:
- Line 22: Replace the short bind-mount entry for the host config file with
Docker Compose's long syntax so Docker doesn't auto-create a directory when the
host file is missing; specifically, change the mount that references
${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json to a long-form bind with
type: "bind", source: set to
${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json and target:
"/data/.claude/claude-devtools-config.json" (preserve read/write mode), ensuring
Docker will fail fast if the file is absent rather than creating a directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 565caf4f-6eb4-4d3f-adf4-37e914474efa

📥 Commits

Reviewing files that changed from the base of the PR and between 9745733 and 84a061c.

📒 Files selected for processing (1)
  • docker-compose.yml

Comment thread docker-compose.yml
- "3456:3456"
volumes:
- ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro
- ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Docker Compose (current spec/version), what is the exact behavior when a bind-mount source path targets a file but the source file does not exist? Also confirm support and semantics of bind.create_host_path: false.

💡 Result:

In the current Docker Compose (v5.x as of 2026, following the Compose Specification), when a bind-mount source path targets a file that does not exist on the host: - Using short syntax (e.g., ./myfile.conf:/container/path/myfile.conf): Compose creates a directory at the source path (for backward compatibility with legacy docker-compose). The mount then fails because Docker expects the source to match the target type (file), but a directory was created instead. Result: error "invalid mount config for type 'bind': bind source path does not exist" or similar. - Using long syntax without bind.create_host_path (e.g., type: bind, source: ./myfile.conf, target: /container/path/myfile.conf): Compose does not create the source path. Docker fails the mount with "bind source path does not exist". - To explicitly create the path (mimicking short syntax behavior): bind: { create_host_path: true }. But since the target is a file, creating a directory causes a mismatch and failure. Overall behavior: The operation fails with an error. Docker does not create the missing file; it requires the exact source file to exist beforehand for a successful file bind-mount. If the source is missing, no file is created in the container—either an error occurs or (in directory auto-creation cases) a directory is mounted instead, leading to app failures. bind.create_host_path: false is fully supported in long-syntax bind mounts (per Compose Specification). Semantics: Prevents automatic creation of the missing host source path (default is true). If the path does not exist, the mount fails immediately (error as above). This is useful to enforce that the host file must pre-exist, avoiding accidental directory creation. Note: There was a bug in early v5.x (issue #13602, Feb 2026) where false was ignored, but it was fixed in later releases (v5.1.0+ with updated compose-go deps). Example configs: Short syntax (fails, creates dir): volumes: - ./missing.conf:/app/missing.conf # Creates ./missing.conf/ dir, then fails Long syntax, default (fails, no creation): volumes: - type: bind source: ./missing.conf target: /app/missing.conf With create_host_path: true (fails, creates dir): volumes: - type: bind source: ./missing.conf target: /app/missing.conf bind: create_host_path: true With create_host_path: false (fails explicitly): volumes: - type: bind source: ./missing.conf target: /app/missing.conf bind: create_host_path: false To succeed: Ensure ./missing.conf exists on host first.

Citations:


🏁 Script executed:

# First, examine the docker-compose.yml around the reviewed lines
head -30 docker-compose.yml | tail -15

Repository: matt1398/claude-devtools

Length of output: 545


🏁 Script executed:

# Search for any initialization or setup scripts that might create the config file
fd -type f \( -name "*.sh" -o -name "*.json" -o -name "*.ts" -o -name "*.js" \) | xargs rg -l "claude-devtools-config" 2>/dev/null | head -20

Repository: matt1398/claude-devtools

Length of output: 421


🏁 Script executed:

# Check ConfigManager to see if it auto-creates the file or depends on pre-existing file
rg -A 10 -B 5 "persistConfig" src/main/services/infrastructure/ConfigManager.ts

Repository: matt1398/claude-devtools

Length of output: 707


🏁 Script executed:

# Search for default config or initialization
rg -B 5 -A 5 "configPath\|default.*config\|initialize.*config" src/main/services/infrastructure/ConfigManager.ts | head -50

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Check if there's a setup script or README with initialization instructions
cat README.md 2>/dev/null | grep -A 10 -i "setup\|install\|docker" | head -40

Repository: matt1398/claude-devtools

Length of output: 1490


🏁 Script executed:

# Search for any place where the config file is pre-created or defaults are set
rg -l "\.claude.*json\|configPath.*=" src/ | xargs cat 2>/dev/null | head -100

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Find ConfigManager constructor and full class initialization
rg -B 3 -A 20 "class ConfigManager\|constructor\(" src/main/services/infrastructure/ConfigManager.ts | head -80

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Check if there's a bootstrap or initialization entry point
rg -l "ConfigManager\|bootstrap\|initialize" src/main/*.ts

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Look for any Docker setup documentation or entrypoint script
find . -name "entrypoint.sh" -o -name "docker-entrypoint.sh" -o -name "start.sh" 2>/dev/null

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Get the full ConfigManager.ts to understand initialization
wc -l src/main/services/infrastructure/ConfigManager.ts

Repository: matt1398/claude-devtools

Length of output: 121


🏁 Script executed:

# Read ConfigManager.ts completely
cat -n src/main/services/infrastructure/ConfigManager.ts | head -150

Repository: matt1398/claude-devtools

Length of output: 6071


🏁 Script executed:

# Also check how ConfigManager is instantiated in the main application
rg -B 5 -A 5 "new ConfigManager\|ConfigManager\(" src/main/standalone.ts

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Read the constructor and initialization section
cat -n src/main/services/infrastructure/ConfigManager.ts | sed -n '200,350p'

Repository: matt1398/claude-devtools

Length of output: 5210


🏁 Script executed:

# Search for constructor definition specifically
rg -n "constructor\|loadConfig\|private.*load" src/main/services/infrastructure/ConfigManager.ts | head -20

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Find loadConfig method implementation
rg -n -A 30 "private.*loadConfig\|loadConfig.*Promise" src/main/services/infrastructure/ConfigManager.ts | head -60

Repository: matt1398/claude-devtools

Length of output: 50


🏁 Script executed:

# Search more broadly for loadConfig
rg -n "loadConfig" src/main/services/infrastructure/ConfigManager.ts

Repository: matt1398/claude-devtools

Length of output: 216


🏁 Script executed:

# Get the section around line 332 where loadConfig is called
cat -n src/main/services/infrastructure/ConfigManager.ts | sed -n '330,450p'

Repository: matt1398/claude-devtools

Length of output: 4921


🏁 Script executed:

# Check docker-compose documentation or comments about first-run setup
grep -i "first\|setup\|init\|claude.*dir" docker-compose.yml

Repository: matt1398/claude-devtools

Length of output: 223


🏁 Script executed:

# Check if there's any documentation about CLAUDE_DIR
grep -r "CLAUDE_DIR\|\.claude" . --include="*.md" --include="*.txt" 2>/dev/null | head -20

Repository: matt1398/claude-devtools

Length of output: 5246


Bind-file mount fails on first Docker startup if the config file doesn't exist.

When ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json is missing on the host, Docker Compose's short syntax creates a directory at that path instead of treating it as a file. This causes the mount to fail because the target (/data/.claude/claude-devtools-config.json) expects a file, not a directory—even though ConfigManager.persistConfig can handle the missing file gracefully once the container starts.

To fail fast instead of silently creating a directory, use the long syntax:

Suggested Compose change
     volumes:
       - ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro
-      - ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw
+      - type: bind
+        source: ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json
+        target: /data/.claude/claude-devtools-config.json
+        read_only: false
+        bind:
+          create_host_path: false

This change prevents auto-creation of a directory and makes the error clear. However, users will still need the file to exist beforehand—consider documenting this requirement or auto-creating a default config on first run.

📝 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.

Suggested change
- ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw
- ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro
- type: bind
source: ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json
target: /data/.claude/claude-devtools-config.json
read_only: false
bind:
create_host_path: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` at line 22, Replace the short bind-mount entry for the
host config file with Docker Compose's long syntax so Docker doesn't auto-create
a directory when the host file is missing; specifically, change the mount that
references ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json to a long-form
bind with type: "bind", source: set to
${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json and target:
"/data/.claude/claude-devtools-config.json" (preserve read/write mode), ensuring
Docker will fail fast if the file is absent rather than creating a directory.

@sentisso
Copy link
Copy Markdown
Author

sentisso commented Apr 19, 2026

I figure the code reviews bots do have a point and it might not be that simple as I thought. Two possible solutions:

  1. Instruct the users to create the config file manually before creating the docker container
  2. Move the config file in a sub-folder (e.g. .claude/claude-devtools/config.json). This way the container can just bind the folder .claude/claude-devtools/ in read-write, Docker will create the folder on the host and the config file can be normally created on-demand in the application.

I like solution 2. more, but that would require some deeper changes to the config management and I'm not sure if that's okay to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant