Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
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" \
Copy link
Copy Markdown
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

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

Choose a reason for hiding this comment

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

PROJ-008 (MEDIUM): This creates /commandhistory with bash/zsh history files, but devcontainer.json has no corresponding volume mount for this directory. Without a persistent volume, shell history is lost on every container rebuild — making these lines dead code.

The README also states "No shell history is persisted or shared with the container" (Security Model section), which contradicts this Dockerfile setup.

Suggestion: Either:

  1. Add a history volume in devcontainer.json and configure HISTFILE:

    {
      "source": "devcontainer-platform-shell-history-${devcontainerId}",
      "target": "/commandhistory",
      "type": "volume"
    }

    Then set ENV HISTFILE=/commandhistory/.bash_history in the Dockerfile and update the README accordingly.

  2. Or remove these 4 lines if history persistence is intentionally not desired, keeping the README accurate.

🤖 Claudius the Magnificent AI Code Review


# 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
164 changes: 164 additions & 0 deletions .devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# 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. OAuth login must be done on the **host** — it does not work inside the container (the OAuth callback can't reach localhost in the container).

### Option A: OAuth (recommended)

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

```bash
claude login
```

Your `~/.claude/` config (credentials, skills, plugins) is automatically copied into the container on each rebuild. If tokens expire, re-run `claude login` on the host and rebuild.

### Option B: 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
```

## Authentication Details

Your host's `~/.claude/` directory is mounted read-only into the container. On first create, the `post-create.sh` script:

1. Copies your entire `~/.claude/` config (credentials, skills, plugins, etc.) into a persistent Docker volume
2. Forces `bypassPermissions` mode on top of your settings
3. Skips the safety confirmation prompt

Host config items that reference host-specific paths (MCP servers, hooks, etc.) are copied as-is. They will log warnings if the referenced binaries don't exist in the container — this is harmless.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

## 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PROJ-001 (MEDIUM): These instructions reference "uncomment the firewall COPY/sudoers block" but no such block exists in the current Dockerfile. A developer following this will hit a dead end.

Suggestion: Either:

  1. Add the commented-out COPY and sudoers lines to the Dockerfile so developers can uncomment them, or
  2. Rewrite the instructions to be fully self-contained — explain exactly which lines to add and where:
You'll also need to:
1. Add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile
2. Add `COPY init-firewall.sh /usr/local/bin/init-firewall.sh` to the Dockerfile
3. Add a sudoers entry: `RUN echo "vscode ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/firewall`

🤖 Claudius the Magnificent AI Code Review


## 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 config, credentials, conversation history
- `/commandhistory/` — shell history

## 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"

- Check that `ANTHROPIC_API_KEY` is set in your host shell, or
- Run `claude login` on your host before opening the devcontainer, or
- Run `claude login` inside the container

### MCP server warnings at Claude startup

- Expected if your host config has MCP servers referencing macOS binaries. Harmless — Claude works fine without them.

### `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