For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Create a fully sandboxed Docker environment for autonomous Claude Code sessions with isolated postgres, localstack, and nREPL.
Architecture: Monolith container (Claude + Clojure + nREPL) with sidecar postgres/localstack. Git worktrees for code isolation. Host mounts for auth and dependency caching.
Tech Stack: Docker, docker-compose, Babashka tasks, Clojure nREPL with cider middleware
Files:
sandbox/ directorysandbox/.gitkeepStep 1: Create directory
mkdir -p sandbox
touch sandbox/.gitkeep
Step 2: Commit
git add sandbox/.gitkeep
git commit -m "chore: add sandbox directory"
Files:
sandbox/DockerfileStep 1: Write Dockerfile
FROM eclipse-temurin:21-jdk-alpine
# System dependencies
RUN apk add --no-cache \
nodejs npm \
bash curl git \
netcat-openbsd \
rlwrap
# Install Clojure CLI
RUN curl -L -O https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh \
&& chmod +x linux-install.sh \
&& ./linux-install.sh \
&& rm linux-install.sh
# Install Babashka
RUN curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install \
&& chmod +x install \
&& ./install --dir /usr/local/bin \
&& rm install
# Install bbin
RUN curl -o- -L https://raw.githubusercontent.com/babashka/bbin/v0.2.4/bbin > /usr/local/bin/bbin \
&& chmod +x /usr/local/bin/bbin
# Install clj-nrepl-eval via bbin
ENV BBIN_HOME=/root/.local/share/bbin
ENV PATH="${BBIN_HOME}/bin:${PATH}"
RUN bbin install https://github.com/bhauman/clojure-mcp-light.git \
--as clj-nrepl-eval \
--main-opts '["-m" "clojure-mcp-light.nrepl-eval"]'
# Install Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code
WORKDIR /workspace
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Step 2: Commit
git add sandbox/Dockerfile
git commit -m "feat(sandbox): add Dockerfile with Claude + Clojure + Babashka"
Files:
sandbox/entrypoint.shStep 1: Write entrypoint
#!/bin/bash
set -e
NREPL_PORT="${NREPL_PORT:-7888}"
echo "Starting nREPL on port $NREPL_PORT..."
cd /workspace
# Start nREPL with cider middleware in background
clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.3.1"}
cider/cider-nrepl {:mvn/version "0.56.0"}
refactor-nrepl/refactor-nrepl {:mvn/version "3.11.0"}}
:aliases {:cider/nrepl {:main-opts ["-m" "nrepl.cmdline"
"--middleware" "[refactor-nrepl.middleware/wrap-refactor,cider.nrepl/cider-middleware]"]}}}' \
-M:dev:test:cider/nrepl \
-p "$NREPL_PORT" \
-b 0.0.0.0 &
NREPL_PID=$!
# Wait for nREPL to be ready
echo "Waiting for nREPL..."
until nc -z localhost "$NREPL_PORT" 2>/dev/null; do
sleep 0.5
done
echo "nREPL ready on port $NREPL_PORT"
# Run Claude Code with dangerous permissions (safe in sandbox)
exec claude --dangerously-skip-permissions "$@"
Step 2: Commit
git add sandbox/entrypoint.sh
git commit -m "feat(sandbox): add entrypoint script for nREPL + Claude"
Files:
sandbox/docker-compose.ymlStep 1: Write docker-compose.yml
name: orcha-sandbox-${FEATURE_NAME:-default}
services:
postgres:
image: postgres:18.1
environment:
POSTGRES_DB: orcha
POSTGRES_HOST_AUTH_METHOD: trust
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d orcha"]
interval: 2s
timeout: 5s
retries: 10
start_period: 5s
localstack:
image: localstack/localstack:latest
environment:
- SERVICES=s3,sqs,secretsmanager,ssm
- DEFAULT_REGION=eu-central-1
- PERSISTENCE=1
volumes:
- localstack-data:/var/lib/localstack
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 2s
timeout: 5s
retries: 10
start_period: 10s
claude:
build:
context: .
dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
localstack:
condition: service_healthy
environment:
- ENVIRON_NAME=sandbox
- NREPL_PORT=7888
volumes:
- ${WORKTREE_PATH:-.}:/workspace
- ${HOME}/.claude:/root/.claude:ro
- ${WORKTREE_PATH:-.}/.claude-sandbox/projects:/root/.claude/projects
- ${HOME}/.m2:/root/.m2
- ${HOME}/.gitlibs:/root/.gitlibs
- ${HOME}/.clojure/.cpcache:/root/.clojure/.cpcache
ports:
- "${NREPL_HOST_PORT:-17888}:7888"
stdin_open: true
tty: true
volumes:
postgres-data:
localstack-data:
Step 2: Commit
git add sandbox/docker-compose.yml
git commit -m "feat(sandbox): add docker-compose with postgres, localstack, claude"
Files:
resources/com/getorcha/config.ednStep 1: Add sandbox endpoint for AWS
Find the :com.getorcha/aws section and add :sandbox profile to the endpoint:
:com.getorcha/aws
{:config {:region "eu-central-1"
:endpoint #profile {:local-dev "http://localhost:4566"
:sandbox "http://localstack:4566"
:default nil}}
Step 2: Add sandbox profile for db-credentials-json
Find :com.getorcha/db-credentials-json and add :sandbox profile:
:com.getorcha/db-credentials-json
#profile {:local-dev "{\"host\":\"localhost\",\"port\":5432,\"dbname\":\"orcha\",\"username\":\"postgres\"}"
:sandbox "{\"host\":\"postgres\",\"port\":5432,\"dbname\":\"orcha\",\"username\":\"postgres\"}"
:default #orcha/param "/v1-orcha/db-credentials"}
Step 3: Commit
git add resources/com/getorcha/config.edn
git commit -m "feat(sandbox): add :sandbox profile for Docker networking"
Files:
scripts/sandbox.cljbb.ednStep 1: Write sandbox.clj
(ns sandbox
(:require [babashka.fs :as fs]
[babashka.process :as p]
[clojure.string :as str]))
(def worktree-base ".worktrees/feature")
(def port-range-start 17000)
(def port-range-size 1000)
(defn feature->port
"Hash feature name to port in range 17000-17999"
[feature-name]
(+ port-range-start
(mod (Math/abs (hash feature-name)) port-range-size)))
(defn parse-args
"Parse --port flag from args, return {:port n :feature name}"
[args]
(loop [args args
result {}]
(if (empty? args)
result
(let [[a b & rest] args]
(cond
(= a "--port")
(recur rest (assoc result :port (parse-long b)))
(= a "--worktree")
(recur (cons b rest) (assoc result :worktree true))
(nil? (:feature result))
(recur (cons b rest) (assoc result :feature a))
:else
(recur (cons b rest) result))))))
(defn start
"Start sandbox for feature"
[args]
(let [{:keys [feature port]} (parse-args args)]
(when-not feature
(println "Usage: bb sandbox:start <feature-name> [--port PORT]")
(System/exit 1))
(let [port (or port (feature->port feature))
worktree-path (str worktree-base "/" feature)
abs-worktree-path (str (fs/absolutize worktree-path))
branch-name (str "feature/" feature)]
;; Create worktree if not exists
(when-not (fs/exists? worktree-path)
(println (str "Creating worktree at " worktree-path " from main..."))
(p/shell "git" "worktree" "add" "-b" branch-name worktree-path "main"))
;; Create .claude-sandbox/projects for conversation persistence
(fs/create-dirs (str worktree-path "/.claude-sandbox/projects"))
;; Write port file
(spit (str worktree-path "/.nrepl-port") (str port))
(println (str "nREPL will be exposed on port " port))
(println "Starting sandbox containers...")
;; Start docker-compose
(p/shell {:dir "sandbox"
:extra-env {"WORKTREE_PATH" abs-worktree-path
"NREPL_HOST_PORT" (str port)
"FEATURE_NAME" feature
"HOME" (System/getenv "HOME")}}
"docker-compose" "run" "--rm" "--service-ports" "claude"))))
(defn stop
"Stop sandbox for feature"
[args]
(let [{:keys [feature]} (parse-args args)]
(when-not feature
(println "Usage: bb sandbox:stop <feature-name>")
(System/exit 1))
(println (str "Stopping sandbox for " feature "..."))
(p/shell {:dir "sandbox"
:extra-env {"FEATURE_NAME" feature}}
"docker-compose" "down")))
(defn list-sandboxes
"List running sandboxes"
[_args]
(let [result (p/shell {:out :string}
"docker" "ps" "--filter" "name=orcha-sandbox" "--format" "{{.Names}}")]
(if (str/blank? (:out result))
(println "No running sandboxes")
(do
(println "Running sandboxes:")
(doseq [name (str/split-lines (:out result))]
(println (str " " name)))))))
(defn clean
"Clean up sandbox containers and optionally worktree"
[args]
(let [{:keys [feature worktree]} (parse-args args)]
(when-not feature
(println "Usage: bb sandbox:clean <feature-name> [--worktree]")
(System/exit 1))
(let [worktree-path (str worktree-base "/" feature)
branch-name (str "feature/" feature)]
;; Stop and remove containers/volumes
(println (str "Removing containers and volumes for " feature "..."))
(p/shell {:dir "sandbox"
:extra-env {"FEATURE_NAME" feature}
:continue true}
"docker-compose" "down" "-v")
;; Remove worktree if requested
(when worktree
(when (fs/exists? worktree-path)
(println (str "Removing worktree " worktree-path "..."))
(p/shell "git" "worktree" "remove" "--force" worktree-path))
;; Delete branch
(println (str "Deleting branch " branch-name "..."))
(p/shell {:continue true} "git" "branch" "-D" branch-name))
(println "Done."))))
Step 2: Add tasks to bb.edn
Add the following to the :tasks map in bb.edn:
;; Sandbox tasks
sandbox:start
{:doc "Start sandboxed Claude session: bb sandbox:start <feature-name> [--port PORT]"
:requires ([sandbox])
:task (sandbox/start *command-line-args*)}
sandbox:stop
{:doc "Stop sandbox: bb sandbox:stop <feature-name>"
:requires ([sandbox])
:task (sandbox/stop *command-line-args*)}
sandbox:list
{:doc "List running sandboxes"
:requires ([sandbox])
:task (sandbox/list-sandboxes *command-line-args*)}
sandbox:clean
{:doc "Clean up sandbox: bb sandbox:clean <feature-name> [--worktree]"
:requires ([sandbox])
:task (sandbox/clean *command-line-args*)}
Step 3: Commit
git add scripts/sandbox.clj bb.edn
git commit -m "feat(sandbox): add bb tasks for sandbox management"
Files:
.gitignoreStep 1: Add sandbox-related ignores
Add to .gitignore:
# Sandbox
.claude-sandbox/
.nrepl-port
Step 2: Commit
git add .gitignore
git commit -m "chore: ignore sandbox conversation state"
Step 1: Build the image
cd sandbox && docker-compose build claude
Expected: Image builds successfully
Step 2: Test basic startup
cd sandbox && WORKTREE_PATH=$(pwd)/.. docker-compose run --rm claude echo "Container works"
Expected: Prints "Container works" after nREPL starts
Step 3: Commit if any fixes needed
Step 1: Create test sandbox
bb sandbox:start test-sandbox
Expected:
.worktrees/feature/test-sandbox.worktrees/feature/test-sandbox/.nrepl-portStep 2: Test nREPL connection from host
In another terminal:
PORT=$(cat .worktrees/feature/test-sandbox/.nrepl-port)
clj-nrepl-eval -p $PORT "(+ 1 2 3)"
Expected: 6
Step 3: Exit Claude and clean up
Exit Claude with /exit or Ctrl+D, then:
bb sandbox:clean test-sandbox --worktree
Expected: Containers, volumes, worktree, and branch removed
Files:
CLAUDE.md (add sandbox section)Step 1: Add sandbox documentation to CLAUDE.md
Add a section:
## Sandbox Development
Run Claude Code in an isolated Docker environment:
```bash
bb sandbox:start <feature-name> # Start sandbox with worktree
bb sandbox:stop <feature-name> # Stop containers
bb sandbox:list # List running sandboxes
bb sandbox:clean <name> [--worktree] # Clean up
Connect editor to nREPL:
cat .worktrees/feature/<name>/.nrepl-port
# Connect to localhost:<port>
Inside sandbox, start Integrant system with (go) in nREPL.
**Step 2: Commit**
```bash
git add CLAUDE.md
git commit -m "docs: add sandbox usage instructions"