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
9 changes: 9 additions & 0 deletions .devcontainer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Personal Claude Code agents and skills to copy from your host ~/.claude/ into the container.
# Copy this file to .env and customize. This file is gitignored.
#
# For plugins, use .claude/settings.local.json inside the container instead
# (auto-gitignored by Claude Code).
#
# Comma-separated names (agents without .md extension, skills as directory names)
CLAUDE_AGENTS=
CLAUDE_SKILLS=
4 changes: 4 additions & 0 deletions .devcontainer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
.claude-host-config/
.main-git-resolved
.claude-config-resolved
96 changes: 63 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,85 @@ 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" \
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just call scripts/setup-ai-agent-environment.sh ?

-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

# 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
224 changes: 224 additions & 0 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# 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

### GitHub access (for git push/pull)

The easiest option is to export `GH_TOKEN` (or `GITHUB_TOKEN`) on your host. The devcontainer forwards it and configures HTTPS git automatically — no SSH key required:

```bash
export GH_TOKEN=ghp_... # add to your shell profile
```

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

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

### 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`) are copied into the container. Optionally, personal agents and skills listed in `.devcontainer/.env` are also copied. No conversation history, project memories, settings, or plugins 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

Plugins are **not** copied from your host. Use `.claude/settings.local.json` inside the container to enable personal plugins — this file is automatically gitignored by Claude Code:

```json
{
"enabledPlugins": {
"my-plugin@my-marketplace": true
}
}
```

### Agents & skills

Personal agents and skills are **not** copied automatically. To bring specific ones from your `~/.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 is available inside the container via the workspace bind mount. Project-level agents (`.claude/agents/`) and skills (`.claude/skills/`) are automatically loaded by Claude Code.

## 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, plugins, hooks, scripts, or debug logs are transferred.
- **Agents/skills** are only copied if explicitly listed in `.devcontainer/.env` — nothing personal leaks in by default.
- **A clean `settings.json`** is generated inside the container with `bypassPermissions` — 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.
Copy link
Contributor

Choose a reason for hiding this comment

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


## 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 make two changes to the `Dockerfile`:

1. Add `iptables ipset iproute2 dnsutils` to the `apt-get install` block.
2. After the apt-get block, add:
```dockerfile
COPY init-firewall.sh /usr/local/bin/init-firewall.sh
RUN echo "vscode ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/firewall
```

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
Loading