# Sandbox image for OpenClaw agent runs. One image, full developer toolchain.
#
# Tag: openclaw-sandbox:bookworm-slim — what OpenClaw's
# DEFAULT_SANDBOX_IMAGE (vendor/openclaw/src/agents/sandbox/constants.ts)
# expects. Built by scripts/deploy.sh on every deploy.
#
# Design intent: an agent's container is their workstation. They can
# install things, save files, run servers, edit configs. The image
# pre-loads the toolchain so the most common "I need X" cases don't
# require root or apt; the rest is unblocked once userns-remap is on
# (see docs/runbook/sandbox.md).
#
# Why bookworm-slim base: stable, security-supported, well-known. We
# add ~400MB of toolchain on top — total ~500-600MB. The image is
# stored once per host and shared across every agent on that host;
# per-agent cost is the writable overlay (small unless the agent
# builds heavy things), not the image.
#
# Why python3 is non-negotiable: OpenClaw's edit/write tools route
# through fs-bridge-mutation-helper.ts, which requires python3 inside
# the sandbox to perform atomic file mutations. Without it, every
# agent attempt to edit a workspace file fails with "sandbox pinned
# mutation helper requires python3 or python."

FROM debian:bookworm-slim

ENV DEBIAN_FRONTEND=noninteractive
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

# Core tooling: ca-certs, network clients, version control, ssh, python,
# json/text utilities, build tools, editors, sudo (for root-in-container
# under userns-remap), tmux for long-running sessions agents may host.
RUN apt-get update && apt-get install -y --no-install-recommends \
      ca-certificates \
      curl \
      wget \
      gnupg \
      git \
      openssh-client \
      python3 \
      python3-pip \
      python3-venv \
      pipx \
      jq \
      less \
      make \
      build-essential \
      rsync \
      tmux \
      vim-tiny \
      nano \
      sudo \
      tini \
      procps \
      iproute2 \
 && rm -rf /var/lib/apt/lists/*

# Node.js LTS via NodeSource — current LTS is 22.x as of 2026.
# Node ships corepack which manages pnpm/yarn so agents can use
# whichever package manager their target repo expects without an
# explicit install step.
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
 && apt-get update && apt-get install -y --no-install-recommends nodejs \
 && rm -rf /var/lib/apt/lists/* \
 && corepack enable

# GitHub CLI — agents pushing branches and managing PRs from inside
# the sandbox is a first-class workflow, so `gh` is in the base image.
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
      | gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
 && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
      > /etc/apt/sources.list.d/github-cli.list \
 && apt-get update && apt-get install -y --no-install-recommends gh \
 && rm -rf /var/lib/apt/lists/*

# Identity for uid 1000. Containers are spawned with `--user 1000:1000`
# (see docs/runbook/sandbox.md "Agent root inside container"). Without
# a passwd entry for that uid, every tool that calls getpwuid() —
# ssh, gh, git's known_hosts/credentials lookup, anything that
# expands `~` — crashes with "No user exists for uid 1000" before it
# ever reaches the network. HOME points at /workspace because only
# the workspace bind-mount survives container recreation; that lets
# `~/.config/gh/`, `~/.ssh/`, and `~/.gitconfig` persist across image
# upgrades and container recycles. `--no-create-home` because
# /workspace is provided by the bind-mount at runtime.
RUN groupadd --gid 1000 agent \
 && useradd --uid 1000 --gid 1000 --home /workspace --shell /bin/bash --no-create-home agent

# Sanity probe: every tool we promise is on PATH for both root and
# non-root users. If this stage fails, the build fails — operator
# notices at deploy time, not at agent-turn time.
RUN set -eux; \
    for bin in python3 pip3 pipx git ssh node npm npx corepack pnpm yarn gh \
               jq curl wget make gcc g++ rsync tmux sudo; do \
      command -v "$bin" >/dev/null || { echo "missing: $bin" >&2; exit 1; }; \
    done; \
    echo "image toolchain probe: OK"
