Skip to content

Commit 68114f2

Browse files
committed
Extract shared base image, pin tool versions, add standalone usage docs
- Extract system packages, Go, Go tools, and Node.js into base/Dockerfile - Claude and codex Dockerfiles extend the base, each with its own username - Pin gopls, delve, golangci-lint, and RTK to specific versions - CI workflow builds base first, then sandboxes in parallel - Add "Running standalone" section with credential and mount examples - Add AGENTS.md, CLAUDE.md, .gitignore
1 parent dc48073 commit 68114f2

9 files changed

Lines changed: 310 additions & 165 deletions

File tree

.github/workflows/release.yml

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,43 @@ permissions:
1313
packages: write
1414

1515
jobs:
16-
build-and-push:
16+
build-base:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: docker/setup-qemu-action@v3
21+
- uses: docker/setup-buildx-action@v3
22+
23+
- name: Log in to GHCR
24+
uses: docker/login-action@v3
25+
with:
26+
registry: ghcr.io
27+
username: ${{ github.actor }}
28+
password: ${{ secrets.GITHUB_TOKEN }}
29+
30+
- name: Compute base image tags
31+
id: meta
32+
uses: docker/metadata-action@v5
33+
with:
34+
images: ghcr.io/${{ github.repository_owner }}/sandbox-base
35+
tags: |
36+
type=semver,pattern=v{{version}}
37+
type=semver,pattern=v{{major}}.{{minor}}
38+
type=raw,value=latest
39+
40+
- name: Build and push base
41+
uses: docker/build-push-action@v6
42+
with:
43+
context: base
44+
platforms: linux/amd64,linux/arm64
45+
push: true
46+
tags: ${{ steps.meta.outputs.tags }}
47+
labels: ${{ steps.meta.outputs.labels }}
48+
cache-from: type=gha,scope=sandbox-base
49+
cache-to: type=gha,mode=max,scope=sandbox-base
50+
51+
build-sandboxes:
52+
needs: build-base
1753
strategy:
1854
matrix:
1955
image:
@@ -24,7 +60,6 @@ jobs:
2460
runs-on: ubuntu-latest
2561
steps:
2662
- uses: actions/checkout@v4
27-
2863
- uses: docker/setup-qemu-action@v3
2964
- uses: docker/setup-buildx-action@v3
3065

@@ -53,5 +88,7 @@ jobs:
5388
push: true
5489
tags: ${{ steps.meta.outputs.tags }}
5590
labels: ${{ steps.meta.outputs.labels }}
91+
build-args: |
92+
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/sandbox-base:latest
5693
cache-from: type=gha,scope=${{ matrix.image.name }}
5794
cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

AGENTS.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# AGENTS.md
2+
3+
Instructions for AI agents working on this repository.
4+
5+
## Project Overview
6+
7+
Container images for [Wallfacer](https://github.com/changkun/wallfacer) agent sandboxes. Three images are built from this repo:
8+
9+
- **sandbox-base**: shared base with OS packages, Go, Go tools, Node.js, Python, and RTK
10+
- **sandbox-claude**: extends base with the Claude Code CLI
11+
- **sandbox-codex**: extends base with the OpenAI Codex CLI
12+
13+
## Structure
14+
15+
```
16+
base/Dockerfile Shared base image (Ubuntu 24.04, Go, Node.js, Go tools, RTK)
17+
claude/Dockerfile Claude Code sandbox (FROM base)
18+
claude/entrypoint.sh Claude Code entrypoint
19+
codex/Dockerfile Codex sandbox (FROM base)
20+
codex/entrypoint.sh Codex entrypoint with argument translation
21+
Makefile Build targets (base, claude, codex, clean)
22+
.github/workflows/ CI: build-base then build-sandboxes (multi-arch)
23+
```
24+
25+
## Build Commands
26+
27+
```bash
28+
make # Build all images (base, claude, codex)
29+
make base # Build base image only
30+
make claude # Build claude sandbox (builds base first)
31+
make codex # Build codex sandbox (builds base first)
32+
make clean # Remove all images
33+
make RUNTIME=docker # Use Docker instead of Podman
34+
```
35+
36+
## Conventions
37+
38+
- All shared system-level dependencies (OS packages, Go, Go tools, Node.js) go in `base/Dockerfile`. User creation, RTK, and CLI installs go in each child Dockerfile.
39+
- Each image has its own non-root user (UID 1000): `claude` for sandbox-claude, `codex` for sandbox-codex. Wallfacer hardcodes paths under `/home/claude/` and `/home/codex/` for volume mounts, so these usernames must not change.
40+
- Major Go tools are pinned to specific versions via build ARGs. Utility tools use `@latest`.
41+
- RTK version is pinned via the `RTK_VERSION` build ARG.
42+
- Entrypoints handle RTK init at runtime (not build time) because the config volume is mounted at container start.
43+
- The codex entrypoint translates Claude Code-style flags to Codex CLI format and emits a Claude Code-compatible JSON envelope.
44+
45+
## Entrypoint Contract
46+
47+
Wallfacer expects sandbox images to:
48+
49+
- Use `/workspace` as the working directory
50+
- Run as non-root (UID 1000)
51+
- Accept Claude Code-compatible flags (`-p <prompt>`, `--verbose`, `--output-format stream-json`, `--model <val>`, `--resume <val>`)
52+
- Emit a final JSON line: `{result, session_id, stop_reason, is_error, total_cost_usd, usage}`
53+
54+
## CI/CD
55+
56+
The release workflow (`.github/workflows/release.yml`) runs on version tags (`v*`) and manual dispatch:
57+
58+
1. **build-base**: builds and pushes `sandbox-base` (multi-arch amd64/arm64)
59+
2. **build-sandboxes**: builds and pushes `sandbox-claude` and `sandbox-codex` from the pushed base
60+
61+
Images are published to `ghcr.io/latere-ai/`.

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CLAUDE.md
2+
3+
This file is an alias for [AGENTS.md](./AGENTS.md).

Makefile

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
SHELL := /bin/bash
22
RUNTIME := podman
33

4+
BASE_IMAGE := sandbox-base:latest
5+
BASE_GHCR_IMAGE := ghcr.io/latere-ai/sandbox-base:latest
46
CLAUDE_IMAGE := sandbox-claude:latest
57
CLAUDE_GHCR_IMAGE := ghcr.io/latere-ai/sandbox-claude:latest
68
CODEX_IMAGE := sandbox-codex:latest
79
CODEX_GHCR_IMAGE := ghcr.io/latere-ai/sandbox-codex:latest
810

9-
.PHONY: all claude codex clean
11+
.PHONY: all base claude codex clean
1012

1113
all: claude codex
1214

13-
claude:
14-
$(RUNTIME) build -t $(CLAUDE_IMAGE) -t $(CLAUDE_GHCR_IMAGE) -f claude/Dockerfile claude/
15+
base:
16+
$(RUNTIME) build -t $(BASE_IMAGE) -t $(BASE_GHCR_IMAGE) -f base/Dockerfile base/
1517

16-
codex:
17-
$(RUNTIME) build -t $(CODEX_IMAGE) -t $(CODEX_GHCR_IMAGE) -f codex/Dockerfile codex/
18+
claude: base
19+
$(RUNTIME) build --build-arg BASE_IMAGE=$(BASE_IMAGE) \
20+
-t $(CLAUDE_IMAGE) -t $(CLAUDE_GHCR_IMAGE) -f claude/Dockerfile claude/
21+
22+
codex: base
23+
$(RUNTIME) build --build-arg BASE_IMAGE=$(BASE_IMAGE) \
24+
-t $(CODEX_IMAGE) -t $(CODEX_GHCR_IMAGE) -f codex/Dockerfile codex/
1825

1926
clean:
20-
-$(RUNTIME) rmi $(CLAUDE_IMAGE) $(CLAUDE_GHCR_IMAGE) $(CODEX_IMAGE) $(CODEX_GHCR_IMAGE)
27+
-$(RUNTIME) rmi $(CLAUDE_IMAGE) $(CLAUDE_GHCR_IMAGE) \
28+
$(CODEX_IMAGE) $(CODEX_GHCR_IMAGE) \
29+
$(BASE_IMAGE) $(BASE_GHCR_IMAGE)

README.md

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ Container images for [Wallfacer](https://github.com/changkun/wallfacer) agent sa
44

55
## Images
66

7+
- **sandbox-base**: shared base image with OS packages, Go, Go tools, Node.js, Python, and RTK
8+
`ghcr.io/latere-ai/sandbox-base`
79
- **sandbox-claude**: Claude Code CLI sandbox
810
`ghcr.io/latere-ai/sandbox-claude`
911
- **sandbox-codex**: OpenAI Codex CLI sandbox
1012
`ghcr.io/latere-ai/sandbox-codex`
1113

1214
## What's inside
1315

14-
Both images share a common base (Ubuntu 24.04, multi-arch amd64/arm64):
16+
The base image (Ubuntu 24.04, multi-arch amd64/arm64) provides:
1517

1618
- **OS**: Ubuntu 24.04 with `build-essential`, `git`, `curl`, `wget`, `vim`, `jq`, `ripgrep`, `openssh-client`
1719
- **Go**: 1.25.7 + tooling (gopls, goimports, delve, golangci-lint, staticcheck, gosec, and more)
1820
- **Node.js**: 22 LTS
1921
- **Python**: 3 with pip and venv
2022
- **Non-root user**: UID 1000, passwordless sudo
23+
- **[RTK](https://github.com/rtk-ai/rtk)**: token-optimized CLI proxy for reduced token usage
2124

2225
Image-specific additions:
2326

@@ -26,8 +29,6 @@ Image-specific additions:
2629
- **sandbox-codex**
2730
- [Codex CLI](https://github.com/openai/codex) (`@openai/codex`)
2831

29-
Both images also include [RTK](https://github.com/rtk-ai/rtk), a token-optimized CLI proxy for reduced token usage.
30-
3132
## Using pre-built images
3233

3334
Pre-built multi-arch images are published to GHCR on every release:
@@ -47,15 +48,14 @@ Replace `podman` with `docker` if using Docker.
4748

4849
## Building locally
4950

50-
If you want to customize the images or build from source:
51-
5251
```bash
5352
git clone https://github.com/latere-ai/images.git
5453
cd images
5554

56-
make # Build both images
57-
make claude # Build Claude sandbox only
58-
make codex # Build Codex sandbox only
55+
make # Build all images (base, claude, codex)
56+
make base # Build base image only
57+
make claude # Build Claude sandbox (builds base first)
58+
make codex # Build Codex sandbox (builds base first)
5959
make clean # Remove all images
6060
```
6161

@@ -67,13 +67,98 @@ make RUNTIME=docker
6767

6868
Built images are tagged as both `sandbox-claude:latest` (local) and `ghcr.io/latere-ai/sandbox-claude:latest` (registry name). Wallfacer finds images by the local name, so local builds work without any configuration change.
6969

70+
## Running standalone
71+
72+
You can run these images directly without Wallfacer. Each sandbox needs credentials and a workspace directory mounted into the container.
73+
74+
### Claude sandbox
75+
76+
Claude Code authenticates via either an OAuth token or an API key. Pass credentials through an env file and mount a named volume for Claude's config directory.
77+
78+
1. Create an env file (e.g. `~/.claude-sandbox.env`):
79+
80+
```
81+
ANTHROPIC_API_KEY=sk-ant-...
82+
```
83+
84+
Or, if using OAuth (from `claude setup-token`):
85+
86+
```
87+
CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token
88+
```
89+
90+
2. Run the container:
91+
92+
```bash
93+
docker run --rm -it \
94+
--env-file ~/.claude-sandbox.env \
95+
-v claude-config:/home/claude/.claude \
96+
-v "$(pwd)":/workspace/myproject \
97+
-w /workspace/myproject \
98+
ghcr.io/latere-ai/sandbox-claude:latest \
99+
-p "explain this project"
100+
```
101+
102+
The named volume `claude-config` persists Claude's session data, settings, and CLAUDE.md cache between runs. The first run may take a moment while Claude Code initializes.
103+
104+
To start an interactive session instead of a one-shot prompt:
105+
106+
```bash
107+
docker run --rm -it \
108+
--env-file ~/.claude-sandbox.env \
109+
-v claude-config:/home/claude/.claude \
110+
-v "$(pwd)":/workspace/myproject \
111+
-w /workspace/myproject \
112+
--entrypoint claude \
113+
ghcr.io/latere-ai/sandbox-claude:latest
114+
```
115+
116+
### Codex sandbox
117+
118+
Codex authenticates via an API key passed through an env file. If you have logged in with `codex` on the host, you can also bind-mount the auth cache.
119+
120+
1. Create an env file (e.g. `~/.codex-sandbox.env`):
121+
122+
```
123+
OPENAI_API_KEY=sk-...
124+
```
125+
126+
2. Run the container:
127+
128+
```bash
129+
docker run --rm -it \
130+
--env-file ~/.codex-sandbox.env \
131+
-v "$(pwd)":/workspace/myproject \
132+
-w /workspace/myproject \
133+
ghcr.io/latere-ai/sandbox-codex:latest \
134+
-p "explain this project"
135+
```
136+
137+
Or, if you have `~/.codex/auth.json` from a prior `codex` login on the host, mount it read-only instead of using an env file:
138+
139+
```bash
140+
docker run --rm -it \
141+
-v ~/.codex:/home/codex/.codex:ro \
142+
-v "$(pwd)":/workspace/myproject \
143+
-w /workspace/myproject \
144+
ghcr.io/latere-ai/sandbox-codex:latest \
145+
-p "explain this project"
146+
```
147+
148+
### Notes
149+
150+
- Replace `docker` with `podman` if preferred.
151+
- The `-p "..."` flag passes a one-shot prompt. The entrypoint translates this into the appropriate CLI invocation.
152+
- Mount additional project directories as needed under `/workspace/`.
153+
- To limit resources: `--cpus 2 --memory 4g`.
154+
70155
## Entrypoint contract
71156

72-
Wallfacer expects the following from sandbox images:
157+
These details are relevant if you are building custom images on top of the sandboxes or integrating them into your own orchestration.
73158

74159
- **Working directory**: `/workspace` (workspaces are mounted as subdirectories)
75160
- **User**: non-root (UID 1000)
76-
- **Entrypoint**: accepts Claude Code-compatible flags (`-p <prompt>`, `--verbose`, `--output-format stream-json`, `--model <model>`, `--resume <session>`)
161+
- **Entrypoint**: accepts Claude Code-compatible flags (`-p <prompt>`, `--verbose`, `--output-format stream-json`, `--model <val>`, `--resume <val>`)
77162
- **Output**: last line of stdout must be a JSON object with `{result, session_id, stop_reason, is_error, total_cost_usd, usage}`
78163
- **Environment variables**: receives `ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN` (Claude) or `OPENAI_API_KEY` (Codex) via `--env-file`
79-
- **Config volume**: Claude config mounted at `/home/<user>/.claude`
164+
- **Config volume**: Claude config at `/home/claude/.claude`, Codex auth at `/home/codex/.codex`

base/Dockerfile

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
FROM ubuntu:24.04
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
ENV TZ=UTC
5+
6+
# --- System packages ---
7+
RUN apt-get update && apt-get install -y --no-install-recommends \
8+
build-essential \
9+
git \
10+
curl \
11+
wget \
12+
vim \
13+
jq \
14+
unzip \
15+
zip \
16+
ca-certificates \
17+
gnupg \
18+
openssh-client \
19+
sudo \
20+
locales \
21+
ripgrep \
22+
python3 \
23+
python3-pip \
24+
python3-venv \
25+
&& locale-gen en_US.UTF-8 \
26+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
27+
28+
ENV LANG=en_US.UTF-8
29+
ENV LC_ALL=en_US.UTF-8
30+
31+
# --- Go ---
32+
ARG GO_VERSION=1.25.7
33+
RUN ARCH=$(dpkg --print-architecture) && \
34+
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
35+
| tar -C /usr/local -xzf - && \
36+
ln -s /usr/local/go/bin/go /usr/local/bin/go && \
37+
ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt
38+
39+
# --- Go tools ---
40+
# Major tools are pinned to specific versions for reproducibility.
41+
# Utility tools use @latest (stable, rarely breaking).
42+
ARG GOPLS_VERSION=v0.21.1
43+
ARG DLV_VERSION=v1.24.2
44+
ARG GOLANGCI_LINT_VERSION=v2.11.3
45+
ENV GOPATH=/root/go
46+
ENV PATH=$PATH:/root/go/bin
47+
RUN go install golang.org/x/tools/gopls@${GOPLS_VERSION} && \
48+
go install golang.org/x/tools/cmd/goimports@latest && \
49+
go install golang.org/x/tools/cmd/gorename@latest && \
50+
go install golang.org/x/tools/cmd/godoc@latest && \
51+
go install github.com/go-delve/delve/cmd/dlv@${DLV_VERSION} && \
52+
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${GOLANGCI_LINT_VERSION} && \
53+
go install honnef.co/go/tools/cmd/staticcheck@latest && \
54+
go install github.com/fatih/gomodifytags@latest && \
55+
go install github.com/josharian/impl@latest && \
56+
go install github.com/cweill/gotests/gotests@latest && \
57+
go install github.com/rogpeppe/godef@latest && \
58+
go install github.com/ramya-rao-a/go-outline@latest && \
59+
go install github.com/securego/gosec/v2/cmd/gosec@latest && \
60+
rm -rf /root/go/pkg /root/.cache/go-build
61+
# Move tools to /usr/local so they are available to all users
62+
RUN cp /root/go/bin/* /usr/local/bin/ && rm -rf /root/go
63+
ENV GOPATH=
64+
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
65+
66+
# --- Node.js 22 LTS ---
67+
ARG NODE_MAJOR=22
68+
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
69+
apt-get install -y nodejs && \
70+
apt-get clean && rm -rf /var/lib/apt/lists/*
71+
72+
# --- Workspace ---
73+
RUN mkdir -p /workspace
74+
75+
WORKDIR /workspace

0 commit comments

Comments
 (0)