Skip to content

Commit 410b57d

Browse files
Add devcontainer for sandboxed Claude Code development
Provides a containerized dev environment so Claude Code can be run with --dangerously-skip-permissions while confining the blast radius to the container's filesystem. - Base on Microsoft's digest-pinned devcontainers/base:ubuntu24.04, which ships a non-root `vscode` user (UID 1000) with passwordless sudo. - Install the project toolchain via single-user Nix + direnv/nix-direnv, matching the project's flake (python, uv, make, wabt, K Framework). - Install the Claude Code CLI to a per-user npm prefix. - init: true for proper PID 1 / zombie reaping. - Share the host git identity read-only and expose it through a writable GIT_CONFIG_GLOBAL (~/.gitconfig.local) so every user can git commit and run git config inside the container; openssh-client enables SSH remotes via the forwarded host SSH agent. Claude Code runs in its normal permission-prompting mode by default; users opt into bypass per session with --dangerously-skip-permissions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d6fd0d7 commit 410b57d

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Microsoft's official devcontainer base (Ubuntu 24.04), pinned by digest for
2+
# reproducibility. It ships a non-root `vscode` user (UID 1000) with passwordless
3+
# sudo plus the common dev tools (git, curl, sudo, ca-certificates, openssh-client),
4+
# so no manual user creation is needed here.
5+
FROM mcr.microsoft.com/devcontainers/base:ubuntu24.04@sha256:4bcb1b466771b1ba1ea110e2a27daea2f6093f9527fb75ee59703ec89b5561cb
6+
7+
ENV DEBIAN_FRONTEND=noninteractive \
8+
LANG=C.UTF-8 \
9+
LC_ALL=C.UTF-8
10+
11+
# The base already provides git/sudo/ca-certificates/curl/openssh-client; we only
12+
# strictly need xz-utils for the Nix installer tarball. The rest are listed
13+
# defensively (apt is idempotent and skips already-present packages).
14+
RUN apt-get update && apt-get install -y --no-install-recommends \
15+
git sudo ca-certificates curl less xz-utils openssh-client \
16+
&& rm -rf /var/lib/apt/lists/*
17+
18+
# Pre-create /nix owned by the vscode user and a system-wide nix.conf. Two things to note:
19+
# - Sandboxing relies on CAP_SYS_ADMIN (sethostname etc.), which container
20+
# builds don't grant — without `sandbox = false` both the Nix self-install
21+
# and every subsequent `nix profile install` fail.
22+
# - The K Framework binary caches let `kup install k` pull prebuilt K binaries
23+
# instead of compiling from source. Single-user Nix (vscode owns /nix) honors
24+
# these substituters from the system config, so no per-user nix.conf needed.
25+
RUN mkdir -m 0755 /nix \
26+
&& chown vscode:vscode /nix \
27+
&& mkdir -p /etc/nix \
28+
&& printf '%s\n' \
29+
'sandbox = false' \
30+
'filter-syscalls = false' \
31+
'experimental-features = nix-command flakes' \
32+
'substituters = https://cache.nixos.org https://k-framework.cachix.org https://k-framework-binary.cachix.org' \
33+
'trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= k-framework.cachix.org-1:jeyMXB2h28gpNRjuVkehg+zLj62ma1RnyyopA/20yFE= k-framework-binary.cachix.org-1:pJedQ8iG19BW3v/DMMmiRVtwRBGO3fyMv2Ws0OpBADs=' \
34+
> /etc/nix/nix.conf
35+
36+
USER vscode
37+
WORKDIR /home/vscode
38+
39+
# Single-user Nix install — the variant that works in containers without
40+
# systemd / a running daemon.
41+
RUN curl -fsSL https://nixos.org/nix/install | sh -s -- --no-daemon --no-channel-add
42+
43+
ENV PATH=/home/vscode/.nix-profile/bin:/home/vscode/.npm-global/bin:$PATH
44+
45+
# Pin the `nixpkgs` flake to nixos-25.05 (matches the project's flake.nix), then
46+
# install only the container-level bootstrap/runtime tooling via Nix:
47+
# - direnv + nix-direnv → auto-load and cache the flake's `nix develop` shell on
48+
# entry to /workspace; this is what actually provides the
49+
# project toolchain (python, uv, make, wabt, K Framework),
50+
# so those live in flake.nix's devShell, not here.
51+
# - nodejs_22 → host for the Claude Code CLI (run as `claude` from
52+
# anywhere, so it stays globally on PATH, not in the shell)
53+
RUN . /home/vscode/.nix-profile/etc/profile.d/nix.sh \
54+
&& nix registry add nixpkgs github:NixOS/nixpkgs/nixos-25.05 \
55+
&& nix profile install \
56+
nixpkgs#direnv \
57+
nixpkgs#nix-direnv \
58+
nixpkgs#nodejs_22
59+
60+
# Claude Code CLI — installed to a per-user prefix so root isn't required.
61+
RUN . /home/vscode/.nix-profile/etc/profile.d/nix.sh \
62+
&& npm config set prefix /home/vscode/.npm-global \
63+
&& npm install -g @anthropic-ai/claude-code
64+
65+
# Wire up nix-direnv, then hook direnv into interactive shells. Order matters:
66+
# Nix must be sourced before the direnv hook so the profile PATH is in place when
67+
# direnv evaluates the flake.
68+
RUN mkdir -p /home/vscode/.config/direnv \
69+
&& echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > /home/vscode/.config/direnv/direnvrc \
70+
&& printf '%s\n' \
71+
'. /home/vscode/.nix-profile/etc/profile.d/nix.sh' \
72+
'eval "$(direnv hook bash)"' \
73+
>> /home/vscode/.bashrc
74+
75+
WORKDIR /workspace

.devcontainer/devcontainer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "komet_node",
3+
"build": {
4+
"dockerfile": "Dockerfile"
5+
},
6+
"remoteUser": "vscode",
7+
"workspaceFolder": "/workspace",
8+
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind",
9+
"init": true,
10+
"mounts": [
11+
"source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume",
12+
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly"
13+
],
14+
"containerEnv": {
15+
"CLAUDE_CONFIG_DIR": "/home/vscode/.claude",
16+
"GIT_CONFIG_GLOBAL": "/home/vscode/.gitconfig.local"
17+
},
18+
"postCreateCommand": "sudo chown -R vscode:vscode /home/vscode/.claude && bash .devcontainer/setup-git.sh && direnv allow /workspace && nix develop /workspace --command bash -c 'uv sync --frozen'",
19+
"customizations": {
20+
"vscode": {
21+
"extensions": [
22+
"anthropic.claude-code",
23+
"mkhl.direnv"
24+
]
25+
}
26+
}
27+
}

.devcontainer/setup-git.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
# Wire up a writable global git config that inherits the host user's identity.
3+
#
4+
# devcontainer.json bind-mounts the host ~/.gitconfig read-only at
5+
# /home/vscode/.gitconfig and sets GIT_CONFIG_GLOBAL=/home/vscode/.gitconfig.local.
6+
# Git therefore writes global config to the (writable) .gitconfig.local while
7+
# still reading the host's name/email/aliases through the include set up below.
8+
#
9+
# The result: every user of this container can `git commit` with their own host
10+
# identity and run `git config --global ...` inside the container, without the
11+
# EBUSY breakage a directly-writable bind mount over ~/.gitconfig would cause.
12+
#
13+
# Adapted from trailofbits/claude-code-devcontainer's post_install.py. Invoked
14+
# from devcontainer.json's postCreateCommand. Idempotent — safe to re-run.
15+
set -euo pipefail
16+
17+
host_gitconfig="/home/vscode/.gitconfig"
18+
local_gitconfig="/home/vscode/.gitconfig.local"
19+
20+
if [ -f "${host_gitconfig}" ]; then
21+
# Host provided a ~/.gitconfig (bind-mounted as a regular file): inherit it.
22+
printf '[include]\n\tpath = %s\n' "${host_gitconfig}" > "${local_gitconfig}"
23+
echo "setup-git: global config includes host identity from ${host_gitconfig}"
24+
else
25+
# No host ~/.gitconfig — the missing bind source is materialized as an empty
26+
# directory, which must NOT be included. Leave an empty writable global.
27+
: > "${local_gitconfig}"
28+
echo "setup-git: no host ~/.gitconfig found; created empty ${local_gitconfig}"
29+
echo "setup-git: set your identity with 'git config --global user.name ...' / user.email"
30+
fi

0 commit comments

Comments
 (0)