fix(Docker): Add claude-devtools-config.json volume to Docker Compose#179
fix(Docker): Add claude-devtools-config.json volume to Docker Compose#179sentisso wants to merge 1 commit intomatt1398:mainfrom
Conversation
There was a problem hiding this comment.
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.
| - "3456:3456" | ||
| volumes: | ||
| - ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro | ||
| - ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw |
There was a problem hiding this comment.
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.
📝 WalkthroughWalkthroughAdded a bind-mount volume configuration in docker-compose.yml for the Changes
Possibly related issues
Suggested labels
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: 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
| - "3456:3456" | ||
| volumes: | ||
| - ${CLAUDE_DIR:-~/.claude}:/data/.claude:ro | ||
| - ${CLAUDE_DIR:-~/.claude}/claude-devtools-config.json:/data/.claude/claude-devtools-config.json:rw |
There was a problem hiding this comment.
🧩 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:
- 1: [BUG] Bind mounts for which source path does not exists fails on 2.35.0 docker/compose#12735
- 2: [BUG] bind.create_host_path: false is ignored and missing host path is still auto-created docker/compose#13602
- 3: [BUG] Possible regression creating host paths by default for bind mounts docker/compose#12797
- 4: https://github.com/compose-spec/compose-spec/blob/main/spec.md
- 5: https://github.com/compose-spec/compose-spec/blob/main/07-volumes.md
- 6: https://openillumi.com/en/en-docker-bind-mount-file-as-directory-trap/
- 7: https://stackoverflow.com/questions/66324505/docker-compose-mount-single-file-with-directory
- 8: https://stackoverflow.com/questions/42248198/how-to-mount-a-single-file-in-a-volume
🏁 Script executed:
# First, examine the docker-compose.yml around the reviewed lines
head -30 docker-compose.yml | tail -15Repository: 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 -20Repository: 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.tsRepository: 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 -50Repository: 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 -40Repository: 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 -100Repository: 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 -80Repository: 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/*.tsRepository: 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/nullRepository: matt1398/claude-devtools
Length of output: 50
🏁 Script executed:
# Get the full ConfigManager.ts to understand initialization
wc -l src/main/services/infrastructure/ConfigManager.tsRepository: matt1398/claude-devtools
Length of output: 121
🏁 Script executed:
# Read ConfigManager.ts completely
cat -n src/main/services/infrastructure/ConfigManager.ts | head -150Repository: 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.tsRepository: 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 -20Repository: 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 -60Repository: matt1398/claude-devtools
Length of output: 50
🏁 Script executed:
# Search more broadly for loadConfig
rg -n "loadConfig" src/main/services/infrastructure/ConfigManager.tsRepository: 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.ymlRepository: 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 -20Repository: 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: falseThis 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.
| - ${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.
|
I figure the code reviews bots do have a point and it might not be that simple as I thought. Two possible solutions:
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. |
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:
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