init: Coder compose + templates + act-runner

This commit is contained in:
2026-05-23 09:46:51 +02:00
parent 15eba016fb
commit 16708d3a39
9 changed files with 409 additions and 1 deletions
+39
View File
@@ -0,0 +1,39 @@
FROM ghcr.io/astral-sh/uv:bookworm-slim
ARG PY_VERSION=3.13
# minimal OS deps: external network tools + ca-certs + a shell-friendly setup
RUN apt-get update -qq \
&& apt-get install -y --no-install-recommends \
curl wget ca-certificates sudo git openssh-client \
bash less procps file \
&& rm -rf /var/lib/apt/lists/*
# install pinned-but-current Python via uv (rebuild image to bump)
RUN uv python install --default ${PY_VERSION}
# create a managed venv and put it on PATH so `python`/`pip` are the sandbox's
ENV VIRTUAL_ENV=/opt/venv \
PATH=/opt/venv/bin:/root/.local/bin:${PATH}
RUN uv venv --python ${PY_VERSION} /opt/venv
# sci/plotting stack — installed once into /opt/venv
RUN uv pip install --no-cache \
numpy \
pandas \
matplotlib \
plotly \
seaborn \
scipy \
scikit-learn \
requests \
httpx \
ipython \
&& python -c "import numpy, pandas, matplotlib, scipy, sklearn, plotly, seaborn; print('sci stack OK')"
# non-root sandbox user
RUN useradd -m -s /bin/bash -u 1000 coder \
&& echo "coder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder
USER coder
WORKDIR /home/coder
+31
View File
@@ -0,0 +1,31 @@
# mcp-sandbox
Ephemeral, minimalist Python sandbox for MCP-driven code execution (plotting, quick analysis, exfil-style external fetches with `curl`/`wget`/`httpx`).
## What's baked in
- Debian slim + uv + Python 3.13 (rebuild image to bump)
- Sci stack: numpy, pandas, matplotlib, plotly, seaborn, scipy, scikit-learn
- HTTP: requests, httpx, curl, wget
- Tools: git, ssh, ipython, sudo
- Non-root user `coder` (uid 1000) with passwordless sudo
## Why "baked in" vs install-at-startup
MCP spawns short-lived workspaces. Installing deps every time burns ~2 min and ~300 MB of network. Bake them, spin in ~3 s.
## Build the image (run on CT 111)
```bash
cd /opt/stacks/coder/templates/mcp-sandbox
docker build -t coder-mcp-sandbox:latest .
# bump Python: docker build --build-arg PY_VERSION=3.14 -t coder-mcp-sandbox:latest .
```
## Push the template to Coder
```bash
coder templates push mcp-sandbox -d /opt/stacks/coder/templates/mcp-sandbox
```
## Ephemerality
`$HOME` is **not** persisted. Each workspace start gets fresh state. Plots written to `~/plots/` vanish on stop — copy via `coder cp` or upload elsewhere from inside the workspace if you need to keep them.
## GPU
`/dev/dri/renderD128` is mounted. Useful if matplotlib offloads to GPU or if you decide to add `torch[cpu]``torch` with Arc support later.
+72
View File
@@ -0,0 +1,72 @@
terraform {
required_providers {
coder = { source = "coder/coder" }
docker = { source = "kreuzwerker/docker" }
}
}
provider "docker" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
locals {
username = data.coder_workspace_owner.me.name
}
resource "coder_agent" "main" {
arch = "amd64"
os = "linux"
startup_script = <<-EOT
set -e
mkdir -p ~/sandbox ~/plots
echo "ready: $(python --version) | sci stack pre-baked"
EOT
metadata {
display_name = "CPU"
key = "cpu"
script = "coder stat cpu"
interval = 10
timeout = 1
}
metadata {
display_name = "RAM"
key = "ram"
script = "coder stat mem"
interval = 10
timeout = 1
}
}
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = "coder-mcp-sandbox:latest" # built locally; do not pull
name = "coder-${local.username}-${lower(data.coder_workspace.me.name)}-sbx"
hostname = data.coder_workspace.me.name
entrypoint = ["sh", "-c", coder_agent.main.init_script]
env = [
"CODER_AGENT_TOKEN=${coder_agent.main.token}",
"OPENAI_API_KEY=sk-tapirnase",
"OPENAI_BASE_URL=http://192.168.1.40:14000/v1",
"OPENAI_MODEL=qwen3-coder-30b-a3b-instruct",
"AIDER_MODEL=qwen3-coder-30b-a3b-instruct",
"AIDER_WEAK_MODEL=mistral-small-latest",
"EMBEDDING_MODEL=text-embedding-3-large",
"MPLBACKEND=Agg",
]
# ephemeral home — fresh state every spin
# (drop the `volumes` block entirely to keep tmpfs-like behavior)
devices { host_path = "/dev/dri/renderD128" }
memory = 4096
cpu_shares = 512
host {
host = "host.docker.internal"
ip = "host-gateway"
}
}