Skip to content

Commit cb8ede9

Browse files
blooopclaude
andcommitted
fix: make devcontainer portable across hosts
- Add init-host.sh to create ~/.claude directory on host before container starts - Use bind mount for ~/.claude folder instead of individual file mounts - Switch from npm to pixi for claude installation (claude-shim package) - Remove Node.js dependency from claude-code feature - Add pixi bin paths to .profile for proper PATH setup - Add initializeCommand to devcontainer.json to run host init script This allows the devcontainer to work on fresh hosts without requiring pre-existing ~/.claude files, while still sharing credentials across all devcontainers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a989caf commit cb8ede9

5 files changed

Lines changed: 52 additions & 70 deletions

File tree

‎.devcontainer/Dockerfile‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ RUN curl -L -o /usr/local/bin/pixi -fsSL --compressed "https://github.com/prefix
1010
USER vscode
1111
WORKDIR /home/vscode
1212

13-
RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc
13+
RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc \
14+
&& echo 'export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile
1415

1516
# Create .ssh directory with proper permissions for SSH config mounts
1617
RUN mkdir -p /home/vscode/.ssh && chmod 700 /home/vscode/.ssh
Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "Claude Code CLI",
33
"id": "claude-code",
4-
"version": "0.1.0",
5-
"description": "Installs Claude Code CLI globally and mounts configuration directories",
4+
"version": "0.2.0",
5+
"description": "Installs Claude Code CLI via pixi with persistent configuration",
66
"options": {},
77
"documentationURL": "https://github.com/anthropics/devcontainer-features",
88
"licenseURL": "https://github.com/anthropics/devcontainer-features/blob/main/LICENSE",
@@ -16,16 +16,7 @@
1616
"containerEnv": {
1717
"CLAUDE_CONFIG_DIR": "/home/vscode/.claude"
1818
},
19-
"dependsOn": {
20-
"ghcr.io/devcontainers/features/node": {}
21-
},
2219
"mounts": [
23-
"source=${localEnv:HOME}/.claude/CLAUDE.md,target=/home/vscode/.claude/CLAUDE.md,type=bind,ro",
24-
"source=${localEnv:HOME}/.claude/settings.json,target=/home/vscode/.claude/settings.json,type=bind,ro",
25-
"source=${localEnv:HOME}/.claude/.credentials.json,target=/home/vscode/.claude/.credentials.json,type=bind",
26-
"source=${localEnv:HOME}/.claude/.claude.json,target=/home/vscode/.claude/.claude.json,type=bind",
27-
"source=${localEnv:HOME}/.claude/agents,target=/home/vscode/.claude/agents,type=bind,ro",
28-
"source=${localEnv:HOME}/.claude/commands,target=/home/vscode/.claude/commands,type=bind,ro",
29-
"source=${localEnv:HOME}/.claude/hooks,target=/home/vscode/.claude/hooks,type=bind,ro"
20+
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind"
3021
]
3122
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
# Initialize Claude Code host directory for devcontainer bind mount
3+
# This script runs on the HOST before the container is created.
4+
# mkdir -p is idempotent - it only creates if missing, won't clobber existing.
5+
6+
mkdir -p "$HOME/.claude"

‎.devcontainer/claude-code/install.sh‎

Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,59 @@
22
set -eu
33

44
# Claude Code CLI Local Feature Install Script
5-
# Based on: https://github.com/anthropics/devcontainer-features/pull/25
6-
# Combines CLI installation with configuration directory setup
5+
# Installs Claude Code via pixi and sets up configuration directories
76

8-
# Function to install Claude Code CLI
7+
# Function to install Claude Code CLI via pixi
98
install_claude_code() {
10-
echo "Installing Claude Code CLI globally..."
9+
echo "Installing Claude Code CLI via pixi..."
1110

12-
# Verify Node.js and npm are available (should be installed via dependsOn)
13-
if ! command -v node >/dev/null || ! command -v npm >/dev/null; then
11+
# Verify pixi is available
12+
if ! command -v pixi >/dev/null; then
1413
cat <<EOF
1514
16-
ERROR: Node.js and npm are required but not found!
15+
ERROR: pixi is required but not found!
1716
18-
This should not happen as the Node.js feature is declared in 'dependsOn'.
19-
20-
Please check:
21-
1. The devcontainer feature specification is correct
22-
2. The Node.js feature (ghcr.io/devcontainers/features/node) is available
23-
3. Your devcontainer build logs for errors
17+
Please ensure pixi is installed in your base image.
18+
See: https://pixi.sh/latest/
2419
2520
EOF
2621
exit 1
2722
fi
2823

29-
# Install with npm
30-
npm install -g @anthropic-ai/claude-code
24+
# Determine target user for pixi global install
25+
local target_user="${_REMOTE_USER:-vscode}"
26+
local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}"
27+
28+
# Install with pixi global from blooop channel
29+
# Run as target user so it installs to their home directory
30+
if [ "$(id -u)" -eq 0 ] && [ "$target_user" != "root" ]; then
31+
su - "$target_user" -c "pixi global install --channel https://prefix.dev/blooop claude-shim"
32+
else
33+
pixi global install --channel https://prefix.dev/blooop claude-shim
34+
fi
3135

32-
# Verify installation
33-
if command -v claude >/dev/null; then
36+
# Verify installation by checking the binary exists
37+
local pixi_bin_path="$target_home/.pixi/bin"
38+
local claude_bin="$pixi_bin_path/claude"
39+
if [ -x "$claude_bin" ]; then
3440
echo "Claude Code CLI installed successfully!"
35-
claude --version
41+
"$claude_bin" --version
3642
return 0
3743
else
38-
echo "ERROR: Claude Code CLI installation failed!"
44+
echo "ERROR: Claude Code CLI installation failed! Binary not found at $claude_bin"
3945
return 1
4046
fi
4147
}
4248

4349
# Function to create Claude configuration directories
44-
# These directories will be mounted from the host, but we create them
45-
# in the container to ensure they exist and have proper permissions
4650
create_claude_directories() {
4751
echo "Creating Claude configuration directories..."
4852

4953
# Determine the target user's home directory
50-
# $_REMOTE_USER is set by devcontainer, fallback to 'vscode'
5154
local target_user="${_REMOTE_USER:-vscode}"
5255
local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}"
5356

54-
# Be defensive: if the resolved home does not exist, fall back to $HOME,
55-
# then to /home/${target_user}. If neither is available, fail clearly.
57+
# Be defensive: if the resolved home does not exist, fall back
5658
if [ ! -d "$target_home" ]; then
5759
if [ -n "${HOME:-}" ] && [ -d "$HOME" ]; then
5860
echo "Warning: target_home '$target_home' does not exist, falling back to \$HOME: $HOME" >&2
@@ -61,26 +63,21 @@ create_claude_directories() {
6163
echo "Warning: target_home '$target_home' does not exist, falling back to /home/${target_user}" >&2
6264
target_home="/home/${target_user}"
6365
else
64-
echo "Error: No suitable home directory found for '${target_user}'. Tried:" >&2
65-
echo " - _REMOTE_USER_HOME='${_REMOTE_USER_HOME:-}'" >&2
66-
echo " - \$HOME='${HOME:-}'" >&2
67-
echo " - /home/${target_user}" >&2
68-
echo "Please set _REMOTE_USER_HOME to a valid, writable directory." >&2
66+
echo "Error: No suitable home directory found for '${target_user}'." >&2
6967
exit 1
7068
fi
7169
fi
7270

7371
echo "Target home directory: $target_home"
7472
echo "Target user: $target_user"
7573

76-
# Create the main .claude directory
74+
# Create the main .claude directory and subdirectories
7775
mkdir -p "$target_home/.claude"
7876
mkdir -p "$target_home/.claude/agents"
7977
mkdir -p "$target_home/.claude/commands"
8078
mkdir -p "$target_home/.claude/hooks"
8179

8280
# Create empty config files if they don't exist
83-
# This ensures the bind mounts won't fail if files are missing on host
8481
if [ ! -f "$target_home/.claude/.credentials.json" ]; then
8582
echo "{}" > "$target_home/.claude/.credentials.json"
8683
chmod 600 "$target_home/.claude/.credentials.json"
@@ -92,26 +89,28 @@ create_claude_directories() {
9289
fi
9390

9491
# Set proper ownership
95-
# Note: These will be overridden by bind mounts from the host,
96-
# but this ensures the directories exist with correct permissions
97-
# if the mounts fail or for non-mounted directories
9892
if [ "$(id -u)" -eq 0 ]; then
9993
chown -R "$target_user:$target_user" "$target_home/.claude" || true
10094
fi
10195

10296
echo "Claude directories created successfully"
10397
}
10498

105-
# Main script starts here
99+
# Main script
106100
main() {
107101
echo "========================================="
108102
echo "Activating feature 'claude-code' (local)"
109103
echo "========================================="
110104

111-
# Install Claude Code CLI (or verify it's already installed)
112-
if command -v claude >/dev/null; then
105+
# Determine target paths
106+
local target_user="${_REMOTE_USER:-vscode}"
107+
local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}"
108+
local claude_bin="$target_home/.pixi/bin/claude"
109+
110+
# Install Claude Code CLI
111+
if [ -x "$claude_bin" ]; then
113112
echo "Claude Code CLI is already installed"
114-
claude --version
113+
"$claude_bin" --version
115114
else
116115
install_claude_code || exit 1
117116
fi
@@ -123,27 +122,11 @@ main() {
123122
echo "Claude Code feature activated successfully!"
124123
echo "========================================="
125124
echo ""
126-
echo "Configuration files mounted from host:"
127-
echo " Read-Write (auth & state):"
128-
echo " - ~/.claude/.credentials.json (OAuth tokens)"
129-
echo " - ~/.claude/.claude.json (account, setup tracking)"
130-
echo ""
131-
echo " Read-Only (security-protected):"
132-
echo " - ~/.claude/CLAUDE.md"
133-
echo " - ~/.claude/settings.json"
134-
echo " - ~/.claude/agents/"
135-
echo " - ~/.claude/commands/"
136-
echo " - ~/.claude/hooks/"
137-
echo ""
138-
echo "Authentication:"
139-
echo " - If you're already authenticated on your host, credentials are shared"
140-
echo " - Otherwise, run 'claude' and follow the OAuth flow"
141-
echo " - The OAuth callback may open in your host browser"
142-
echo " - Credentials are stored on your host at ~/.claude/.credentials.json"
125+
echo "Configuration is stored in a Docker volume (claude-config)"
126+
echo "and persists between container rebuilds."
143127
echo ""
144-
echo "To modify config files, edit on your host machine and rebuild the container."
128+
echo "To authenticate, run 'claude' and follow the OAuth flow."
145129
echo ""
146130
}
147131

148-
# Execute main function
149132
main

‎.devcontainer/devcontainer.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"dockerfile": "Dockerfile",
55
"context": ".."
66
},
7+
"initializeCommand": ".devcontainer/claude-code/init-host.sh",
78
"customizations": {
89
"vscode": {
910
"settings": {},

0 commit comments

Comments
 (0)