Skip to content

Instantly share code, notes, and snippets.

@philippmuench
Created January 6, 2026 17:38
Show Gist options
  • Select an option

  • Save philippmuench/59f0fdfa957a25ce2141e8b0f274f61c to your computer and use it in GitHub Desktop.

Select an option

Save philippmuench/59f0fdfa957a25ce2141e8b0f274f61c to your computer and use it in GitHub Desktop.
apptainer-ai-sandbox
#!/usr/bin/env bash
set -euo pipefail
# Usage:
# ~/sandbox.sh claude
# ~/sandbox.sh codex
# ~/sandbox.sh claude /path/to/folder
# ~/sandbox.sh codex /path/to/folder
#
# Behavior:
# - Mounts the chosen folder as /work (RW). Defaults to current directory.
# - Uses a persistent sandbox HOME at ~/.sandbox/ai-home for login/config/cache.
# - Pulls a devcontainer image (includes git + node) once into ~/.sandbox/.
if ! command -v apptainer >/dev/null 2>&1; then
echo "Error: apptainer not found in PATH." >&2
exit 1
fi
TOOL="${1:-}"
WORKDIR="${2:-$PWD}"
if [[ -z "${TOOL}" ]]; then
echo "Usage: $0 {claude|codex} [path/to/folder]" >&2
exit 1
fi
case "${TOOL}" in
claude|codex) ;;
*)
echo "Error: tool must be 'claude' or 'codex' (got: ${TOOL})" >&2
exit 1
;;
esac
# Expand ~ in WORKDIR if present
if [[ "${WORKDIR}" == "~"* ]]; then
WORKDIR="${WORKDIR/#\~/$HOME}"
fi
if [[ ! -d "${WORKDIR}" ]]; then
echo "Error: folder does not exist: ${WORKDIR}" >&2
exit 1
fi
# Canonicalize (best-effort; realpath might not exist everywhere)
if command -v realpath >/dev/null 2>&1; then
WORKDIR="$(realpath "${WORKDIR}")"
fi
SANDBOX_BASE="${HOME}/.sandbox"
SANDBOX_HOME="${SANDBOX_BASE}/ai-home"
# Image with git + node + common dev deps
IMAGE_URI="${IMAGE_URI:-docker://mcr.microsoft.com/devcontainers/javascript-node:20}"
SIF="${SIF:-${SANDBOX_BASE}/devcontainers-javascript-node-20.sif}"
mkdir -p "${SANDBOX_BASE}" "${SANDBOX_HOME}"
if [[ ! -f "${SIF}" ]]; then
echo "[sandbox] Pulling image (first run only): ${IMAGE_URI}"
apptainer pull "${SIF}" "${IMAGE_URI}"
fi
exec apptainer exec \
--containall \
--cleanenv \
--home "${SANDBOX_HOME}:/home/sandbox" \
--bind "${WORKDIR}:/work" \
--pwd /work \
--env "TARGET=${TOOL}" \
"${SIF}" \
bash -lc '
set -euo pipefail
export HOME=/home/sandbox
if ! command -v git >/dev/null 2>&1; then
echo "Error: git is not available inside the sandbox (required by Claude marketplaces)." >&2
exit 1
fi
npm config set prefix "$HOME/.npm-global" >/dev/null
export PATH="$HOME/.npm-global/bin:$PATH"
case "${TARGET:-}" in
claude)
if ! command -v claude >/dev/null 2>&1; then
echo "[sandbox] Installing @anthropic-ai/claude-code into $HOME/.npm-global ..."
npm i -g @anthropic-ai/claude-code
fi
echo "[sandbox] Starting Claude in /work ..."
exec claude
;;
codex)
if ! command -v codex >/dev/null 2>&1; then
echo "[sandbox] Installing @openai/codex into $HOME/.npm-global ..."
npm i -g @openai/codex
fi
echo "[sandbox] Starting Codex in /work ..."
exec codex
;;
*)
echo "Internal error: TARGET not set (TARGET=${TARGET:-<empty>})." >&2
exit 2
;;
esac
'
@philippmuench
Copy link
Author

Apptainer Sandbox Runner (Claude + Codex) for Slurm/HPC

This script runs Claude Code or OpenAI Codex CLI inside an Apptainer container for better isolation.
Only the folder you choose is mounted read-write as /work.

Prerequisites

  • apptainer available on the system
  • Outbound internet for the first run (image pull + npm install), unless caches already exist

Install

Save the script as ~/sandbox.sh and make it executable:

chmod +x ~/sandbox.sh

Usage

Start Claude in the current directory (mounted as /work, read-write):

~/sandbox.sh claude

Start Codex in the current directory (mounted as /work, read-write):

~/sandbox.sh codex

Start Claude with an explicit folder mounted as /work (read-write):

~/sandbox.sh claude /path/to/folder

Start Codex with an explicit folder mounted as /work (read-write):

~/sandbox.sh codex /path/to/folder

What gets persisted
• Login/config/cache is stored in: ~/.sandbox/ai-home
• The cached Apptainer image (.sif) is stored in: ~/.sandbox/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment