Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devcontainer/.claude-config-resolved
6 changes: 6 additions & 0 deletions .devcontainer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Claude Code: agents and skills to copy from ~/.claude/ into the container.
# Copy this file to .env and customize. Plugins are always synced automatically.
#
# Comma-separated names (agents without .md extension, skills as directory names)
CLAUDE_AGENTS=
CLAUDE_SKILLS=
3 changes: 3 additions & 0 deletions .devcontainer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.claude-host-config/
.main-git-resolved
101 changes: 68 additions & 33 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Use the official VS Code base image for dev containers
# Dash Platform Dev Container with Claude Code sandbox support
FROM mcr.microsoft.com/devcontainers/base:ubuntu

# Install dependencies
RUN apt-get update && apt-get install -y \
ARG TZ=UTC
ENV TZ="$TZ"

# System dependencies for Rust, RocksDB, and protobuf
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libssl-dev \
pkg-config \
Expand All @@ -13,58 +16,90 @@ RUN apt-get update && apt-get install -y \
gnupg \
lsb-release \
software-properties-common \
unzip
unzip \
libsnappy-dev \
# Developer tools
less \
procps \
fzf \
man-db \
ripgrep \
fd-find \
nano \
vim \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# Switch to clang
RUN rm /usr/bin/cc && ln -s /usr/bin/clang /usr/bin/cc
# Use clang as default C compiler
RUN rm -f /usr/bin/cc && ln -s /usr/bin/clang /usr/bin/cc

# Install protoc - protobuf compiler (pin to 32.0)
# Alpine/system protoc may be outdated; install from releases
# Install protoc (pinned to 32.0)
ARG TARGETARCH
ARG PROTOC_VERSION=32.0
RUN if [[ "$TARGETARCH" == "arm64" ]] ; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \
curl -Ls https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip \
RUN if [ "$TARGETARCH" = "arm64" ]; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \
curl -Ls "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" \
-o /tmp/protoc.zip && \
unzip -qd /opt/protoc /tmp/protoc.zip && \
rm /tmp/protoc.zip && \
ln -s /opt/protoc/bin/protoc /usr/bin/

# Remove duplicate install; single install above is sufficient
# Install git-delta for better diffs
ARG GIT_DELTA_VERSION=0.18.2
RUN ARCH=$(dpkg --print-architecture) && \
curl -L "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" \
-o /tmp/git-delta.deb && \
dpkg -i /tmp/git-delta.deb && \
rm /tmp/git-delta.deb

# Persist bash/zsh history
RUN mkdir -p /commandhistory && \
touch /commandhistory/.bash_history /commandhistory/.zsh_history && \
chown -R vscode:vscode /commandhistory

# Switch to vscode user
# Switch to vscode user for Rust and cargo tools
USER vscode

ENV CARGO_HOME=/home/vscode/.cargo
ENV PATH=$CARGO_HOME/bin:$PATH

# TODO: It doesn't sharing PATH between stages, so we need "source $HOME/.cargo/env" everywhere
COPY rust-toolchain.toml .
RUN TOOLCHAIN_VERSION="$(grep channel rust-toolchain.toml | awk '{print $3}' | tr -d '"')" && \
# Install Rust toolchain from rust-toolchain.toml
COPY --chown=vscode:vscode rust-toolchain.toml /tmp/rust-toolchain.toml
RUN TOOLCHAIN_VERSION="$(grep channel /tmp/rust-toolchain.toml | awk '{print $3}' | tr -d '"')" && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \
-y \
--default-toolchain "${TOOLCHAIN_VERSION}" \
--target wasm32-unknown-unknown
--target wasm32-unknown-unknown && \
rm /tmp/rust-toolchain.toml

# Download and install cargo-binstall
ENV BINSTALL_VERSION=1.10.11
# Install cargo-binstall for fast binary installs
ARG BINSTALL_VERSION=1.10.11
RUN set -ex; \
if [ "$TARGETARCH" = "amd64" ]; then \
if [ "$(uname -m)" = "x86_64" ]; then \
CARGO_BINSTALL_ARCH="x86_64-unknown-linux-musl"; \
elif [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" = "aarch64" ]; then \
elif [ "$(uname -m)" = "aarch64" ]; then \
CARGO_BINSTALL_ARCH="aarch64-unknown-linux-musl"; \
else \
echo "Unsupported architecture: $TARGETARCH"; exit 1; \
echo "Unsupported architecture: $(uname -m)"; exit 1; \
fi; \
DOWNLOAD_URL="https://github.com/cargo-bins/cargo-binstall/releases/download/v${BINSTALL_VERSION}/cargo-binstall-${CARGO_BINSTALL_ARCH}.tgz"; \
curl -L --fail --show-error "$DOWNLOAD_URL" -o /tmp/cargo-binstall.tgz; \
tar -xzf /tmp/cargo-binstall.tgz -C /tmp cargo-binstall; \
chmod +x /tmp/cargo-binstall; \
/tmp/cargo-binstall -y --force cargo-binstall; \
rm /tmp/cargo-binstall; \
cargo binstall -V
curl -L --fail --show-error \
"https://github.com/cargo-bins/cargo-binstall/releases/download/v${BINSTALL_VERSION}/cargo-binstall-${CARGO_BINSTALL_ARCH}.tgz" \
-o /tmp/cargo-binstall.tgz && \
tar -xzf /tmp/cargo-binstall.tgz -C /tmp cargo-binstall && \
chmod +x /tmp/cargo-binstall && \
/tmp/cargo-binstall -y --force cargo-binstall && \
rm /tmp/cargo-binstall.tgz /tmp/cargo-binstall

# Install wasm tools
RUN cargo binstall wasm-bindgen-cli@0.2.108 --locked \
--no-discover-github-token --disable-telemetry --no-track --no-confirm && \
cargo binstall wasm-pack --locked \
--no-discover-github-token --disable-telemetry --no-track --no-confirm

# Prepare directories (need root for /workspace)
USER root
RUN mkdir -p /home/vscode/.claude /workspace && \
chown -R vscode:vscode /home/vscode/.claude /workspace
USER vscode

RUN cargo binstall wasm-bindgen-cli@0.2.103 --locked \
--no-discover-github-token \
--disable-telemetry \
--no-track \
--no-confirm
ENV SHELL=/bin/zsh
ENV EDITOR=nano
ENV VISUAL=nano
203 changes: 203 additions & 0 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Dev Container

Sandboxed development environment for Dash Platform with Claude Code pre-configured for autonomous work.

## What's Included

- **Rust 1.92** with `wasm32-unknown-unknown` target
- **Node.js 24** with yarn 4.12.0 (via corepack)
- **Docker-in-Docker** for dashmate
- **Claude Code** with `bypassPermissions` mode
- protoc 32.0, wasm-bindgen-cli 0.2.108, wasm-pack, cargo-binstall
- Developer tools: git-delta, ripgrep, fd, fzf

## Prerequisites

### SSH keys (for git push/pull)

VS Code forwards your host's SSH agent into the container automatically. Make sure your key is loaded:

```bash
ssh-add --apple-use-keychain ~/.ssh/id_rsa # macOS
ssh-add ~/.ssh/id_rsa # Linux
```

Without this, `git push`/`git pull` will fail with `Permission denied (publickey)`.

### Claude Code authentication

Authenticate using **one or both** methods.

### Option A: OAuth (recommended)

Run on your **host machine** before opening the devcontainer:

```bash
claude login
```

Your OAuth credentials (`~/.claude/.credentials.json`) and enabled plugins are copied into the container. Optionally, agents and skills listed in `.devcontainer/.env` are also copied. No conversation history, project memories, or host settings are transferred. If tokens expire, re-run `claude login` on the host and rebuild.

You can also log in from inside the container using the print-link flow (no browser redirect needed):

```bash
claude login --print-link
```

### Option B: OAuth Token

```bash
export CLAUDE_CODE_OAUTH_TOKEN=<your-token>
```

Set this in your shell profile. The token is forwarded into the container automatically.

### Option C: API Key

```bash
export ANTHROPIC_API_KEY=sk-ant-...
```

Set this in your shell profile so it's available when VS Code launches.

## Usage with VS Code

1. Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension
2. Open this repository in VS Code
3. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) and select **Dev Containers: Reopen in Container**
4. Wait for the build (first time takes a while — Rust toolchain, etc.)
5. Claude Code is ready in the integrated terminal:

```bash
claude # runs with full permissions, no prompts
```

### Personal extensions

The `devcontainer.json` includes shared team extensions (rust-analyzer, eslint, Claude Code, etc.). To add your own extensions to every dev container, set this in your **host** VS Code settings (`Cmd+,` → search "defaultExtensions"):

```json
{
"dev.containers.defaultExtensions": [
"github.copilot",
"vscodevim.vim"
]
}
```

## Usage with CLI (no VS Code)

You can use the [devcontainer CLI](https://github.com/devcontainers/cli) directly:

```bash
# Install the CLI
npm install -g @devcontainers/cli

# Build the container
devcontainer build --workspace-folder .

# Start and enter the container
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . bash

# Run Claude Code directly
devcontainer exec --workspace-folder . claude --dangerously-skip-permissions
```

Or use Docker Compose / `docker exec` if you prefer:

```bash
# Build
devcontainer build --workspace-folder .

# Start in background
devcontainer up --workspace-folder .

# Run Claude in headless mode (for CI/automation)
devcontainer exec --workspace-folder . claude -p "run the test suite" --dangerously-skip-permissions
```

## Claude Code customization

### Plugins

Enabled plugins from your host `~/.claude/settings.json` are automatically synced into the container. No configuration needed — plugin IDs contain no secrets.

### Agents & skills

To copy personal agents or skills from `~/.claude/` into the container, create a `.env` file:

```bash
cp .devcontainer/.env.example .devcontainer/.env
```

Edit `.env` with comma-separated names:

```bash
# Agents from ~/.claude/agents/ (without .md extension)
CLAUDE_AGENTS=blockchain-security-auditor,rust-engineer

# Skills from ~/.claude/skills/ (directory names)
CLAUDE_SKILLS=my-custom-skill
```

The `.env` file is gitignored — each developer configures their own.

### Project-level settings

The project's `.claude/` directory (containing `settings.local.json` and `skills/`) is automatically available inside the container via the workspace bind mount. No extra configuration needed.

## Security Model

Claude Code runs with `bypassPermissions` inside the container — it can read, write, and execute anything. The container is the sandbox boundary. To minimize exposure:

- **Only OAuth credentials** are copied from the host (`~/.claude/.credentials.json`). No conversation history, project memories, settings, hooks, scripts, or debug logs are transferred.
- **Enabled plugins** (just plugin IDs) are synced from host settings. Optionally, listed agents/skills (markdown files only) are copied.
- **A clean `settings.json`** is generated inside the container with `bypassPermissions` and enabled plugins — your host's permission allowlists, MCP server configs, and hooks are not copied.
- **No shell history** is persisted or shared with the container.
- **The `.git` directory** is mounted read-write (required for commits/pushes). This is the main trust boundary — Claude can push code.

## Network Firewall (optional)

By default, the container has unrestricted network access. To enable a restrictive firewall that only allows whitelisted services, add the following to `devcontainer.json`:

```jsonc
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"waitFor": "postStartCommand"
```

You'll also need to add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile and uncomment the firewall COPY/sudoers block. See `init-firewall.sh` for the domain whitelist.

## Persistent Data

These items survive container rebuilds (stored in Docker named volumes):

- `~/.cargo/registry` and `~/.cargo/git` — Rust dependency cache
- `target/` — Rust build artifacts
- `~/.claude/` — Claude Code credentials, settings, and optionally agents/skills from host

## Troubleshooting

### Git worktrees

Git worktrees are supported automatically. The `init-host.sh` script (runs on the host) detects whether you opened a worktree or the main repo and mounts the main `.git` directory into the container. The `post-create.sh` script creates the necessary symlinks so git resolves the worktree paths correctly. Commits and pushes from inside the container work as expected.

### Claude says "not authenticated"

- Ensure `ANTHROPIC_API_KEY` is set in your host shell, **or**
- Run `claude login` on your host and rebuild the container, **or**
- Run `claude login --print-link` inside the container (no browser redirect needed)

### `yarn install` fails

- Run `corepack enable` first (should be done by `post-create.sh`)

### Docker commands fail inside the container

- Docker-in-Docker starts automatically. If it didn't, check `docker info`.

### Firewall too restrictive (if enabled)

- Edit `.devcontainer/init-firewall.sh` to add domains
- Or temporarily flush rules: `sudo iptables -F OUTPUT`
Loading