Claude Code in a Docker sandbox (kept alive with tmux)
I wanted a YOLO sandbox for Claude Code: isolated dependencies, optional CUDA/PyTorch, and a way to keep the agent running even when my SSH session drops. My setup is: Docker for isolation + tmux inside the container for persistence.
Prereqs
- Docker installed
- If you want GPU: Linux + NVIDIA drivers + NVIDIA Container Toolkit
- If you don’t have GPU access: drop
--gpus alland use a non-CUDA base image (e.g.ubuntu:24.04)
Quick GPU sanity check:
docker run --rm --gpus all nvcr.io/nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi
1) Docker image (CUDA + uv + Claude Code + tmux)
This Dockerfile creates an ubuntu user, installs tmux, installs uv + Claude Code, and builds a Python venv (including CUDA PyTorch as an example).
# CUDA dev image (includes compilers + headers). If you don’t need GPU, switch to e.g. ubuntu:24.04.
FROM nvcr.io/nvidia/cuda:12.8.0-cudnn-devel-ubuntu24.04
SHELL ["/bin/bash", "-lc"]
ENV DEBIAN_FRONTEND=noninteractive
# Create a non-root user so the container behaves more like a real dev machine.
# UID=1000 matches the common host user id (helps avoid permission pain on bind mounts).
ARG USER=ubuntu
ARG UID=1000
RUN useradd -m -u "${UID}" -s /bin/bash "${USER}"
# Minimal set of tools I tend to want in a sandbox: build tooling, git, tmux, etc.
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
curl \
git \
nano \
ninja-build \
numactl \
tmux && \
rm -rf /var/lib/apt/lists/*
USER ubuntu
# Put the venv in $HOME and ensure both venv + uv are on PATH for interactive shells.
ENV VIRTUAL_ENV="/home/ubuntu/.venv" \
PATH="/home/ubuntu/.venv/bin:/home/ubuntu/.local/bin:${PATH}" \
TERM=xterm-256color \
COLORTERM=truecolor \
LANG=en_US.UTF-8
# Install uv (fast Python package manager) and Claude Code CLI.
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
curl -fsSL https://claude.ai/install.sh | bash
# Example Python env. Keep/remove depending on whether you need PyTorch in this sandbox.
RUN uv venv "${VIRTUAL_ENV}" --python 3.12 && \
uv pip install cmake && \
uv pip install torch==2.9.0 torchvision==0.24.0 torchaudio==2.9.0 \
--index-url https://download.pytorch.org/whl/cu128
# Optional: a nicer tmux UX for long agent runs.
COPY --chown=ubuntu:ubuntu .tmux.conf /home/ubuntu/.tmux.conf
WORKDIR /workspace
CMD ["/bin/bash"]
2) Build + run helper script
Drop this into docker/run.sh, then chmod +x docker/run.sh.
Assuming you use the same layout, put Dockerfile and .tmux.conf under docker/ as well (the script builds using docker/ as the build context).
Replace IMAGE_NAME / CONTAINER_NAME if you want.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
HOST_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile"
IMAGE_NAME="claude-sandbox:latest"
CONTAINER_NAME="claude-sandbox"
build() {
docker build -t "${IMAGE_NAME}" -f "${DOCKERFILE_PATH}" "${SCRIPT_DIR}"
}
run() {
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true
docker run -dit \
--name "${CONTAINER_NAME}" \
--gpus all \
--shm-size=64g \
-v "${HOST_DIR}:/workspace:rw" \
-w /workspace \
"${IMAGE_NAME}"
echo "Container started: ${CONTAINER_NAME}"
}
attach() {
local user="${1:-ubuntu}"
if [[ "${user}" == "root" ]]; then
docker exec -it -u root -w /workspace "${CONTAINER_NAME}" /bin/bash
else
docker exec -it -u ubuntu -w /workspace "${CONTAINER_NAME}" /bin/bash -lc \
"source /home/ubuntu/.venv/bin/activate && exec /bin/bash"
fi
}
case "${1:-all}" in
build) build ;;
run) run ;;
attach) attach "${2:-ubuntu}" ;;
all) build && run ;;
*) echo "Usage: $0 {build|run|attach [root]|all}" ;;
esac
3) tmux: let Claude keep running
Attach, start a tmux session, run Claude, detach, and reattach later:
./docker/run.sh
./docker/run.sh attach
tmux new -s claude
claude
On the first run you’ll likely need to authenticate:
claude login
Detach with Ctrl-b d, then later:
./docker/run.sh attach
tmux attach -t claude
If you’re intentionally sandboxing and accept the risk, you can also run:
claude --dangerously-skip-permissions
Appendix: .tmux.conf I use
# Apply with: tmux source-file ~/.tmux.conf
set -g default-terminal "xterm-256color"
set-option -ga terminal-overrides ",xterm-256color:Tc"
set -g mouse on
set -g history-limit 10000
setw -q -g utf8 on
# Catppuccin-ish Mocha status bar
set -g status-style "bg=#1e1e2e,fg=#cdd6f4"
set -g window-status-current-style "bg=#89b4fa,fg=#1e1e2e,bold"
set -g pane-border-style "fg=#313244"
set -g pane-active-border-style "fg=#89b4fa"
set -g message-style "bg=#313244,fg=#cdd6f4"
set -g status-left "#[bg=#89b4fa,fg=#1e1e2e,bold] #S #[bg=#1e1e2e,fg=#89b4fa]"
set -g status-right "#[fg=#f5c2e7]#(whoami) #[fg=#89b4fa]│ #[fg=#cba6f7]%Y-%m-%d %H:%M #[bg=#89b4fa,fg=#1e1e2e,bold] #h "
set -g status-left-length 50
set -g status-right-length 100
Comments