Configure Claude Code Sandboxing in a Project Container#

Use this guide to create a sandbox configuration repository for Claude Code and include it in a project container build.

For the full list of quickstarts, see Quickstart Guides.

In this quickstart, you will:

Prerequisites#

Before starting, ensure that:

  1. You have completed Install Claude Code in a Project Container.

  2. You have a GitHub or GitLab account where you can create a public repository.

Key Concepts#

settings.json

Configuration file that Claude reads on session start. Settings follow a precedence hierarchy: managed (server/MDM/file) > CLI args > .claude/settings.local.json (gitignored) > .claude/settings.json (committed) > ~/.claude/settings.json (user level). Arrays merge across scopes (concatenate and deduplicate). Scalars use the highest-priority scope. Deny always wins over allow at any level.

Sandbox Block

Block in settings.json that restricts what files and network endpoints Claude’s processes can access. Uses bubblewrap for filesystem and network isolation and socat for proxied network access through domain allowlists. In container environments, bubblewrap runs in a reduced capability mode (enableWeakerNestedSandbox) that retains filesystem, network and process isolation but cannot mount a fresh /proc.

Permissions Block

Block in settings.json that gates Claude’s tool calls, including Bash. Rules are evaluated in order: deny > ask > allow, first match wins. Bash patterns are glob-style and shell-operator-aware. Read/Edit patterns use gitignore-style globs.

Deny rules on Read and Edit only block Claude’s built-in file tools, not bash subprocesses. For example, Read(./.env) blocks the Read tool but does not prevent cat .env. Use sandbox denyRead rules to enforce OS-level file access restrictions.

Hooks Block

Block in settings.json that defines commands or scripts that run on agent lifecycle events. Claude Code supports 26 hook events. Key events for containers include SessionStart, PreToolUse, PostToolUse, UserPromptSubmit, PermissionRequest and Stop. Hooks can block execution, modify tool input (via updatedInput in the output JSON) and override permission decisions. Matchers use regex syntax, not glob. See the full hooks reference.

Hook Scripts

Standalone bash scripts referenced in the hooks block in settings.json. Easier to use for more complex logic or processes than the individual commands set in settings.json.

AI Workbench Skills Folder

A sub-directory of ~/.claude/skills/ containing a skill package for AI Workbench. Has a SKILL.md file and a references/ folder with supporting documents about the project container and AI Workbench. Claude loads skills automatically when the activation condition in the skill’s frontmatter is met.

Create a Sandbox Configuration Repository#

Step One: Create a public repository with the following structure.

The repository will be cloned to ~/.claude/ inside the container.

nvwb-claude/
├── settings.json                          # Permissions, sandbox, hooks, model
├── hooks/
│   ├── startup-claude.sh                  # SessionStart: inject project context
│   └── block-secrets-in-bash.sh           # PreToolUse: block secret exfiltration
└── skills/
    └── ai-workbench-container/
        ├── SKILL.md                       # Project container rules and constraints
        └── references/
            └── config-files.md            # AI Workbench file reference for Claude
Step Two: Create a settings.json file.

The settings file configures the model, process sandbox, permissions and hooks. Protect it from agent modification by including it in the sandbox denyWrite list.

{
  "model": "claude-opus-4-6",
  "sandbox": {
    "enabled": true,
    "enableWeakerNestedSandbox": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyWrite": [
        "~/.claude/settings.json",
        "~/.claude/skills/ai-workbench-container",
        "~/.claude/hooks",
        "~/.claude.json",
        "~/.claude/credentials.json",
        "~/setup.sh",
        "/project/.claude/settings.local.json",
        "/project/.claude/hooks"
      ],
      "denyRead": [
        "~/.claude.json",
        "~/.claude/credentials.json"
      ]
    },
    "autoAllowBashIfSandboxed": false
  },
  "permissions": {
    "additionalDirectories": [
      "~/",
      "/tmp/",
      "/project/"
    ],
    "ask": [
      "Bash(git add *)",
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(pip install *)",
      "Bash(pip3 install *)",
      "Bash(python -m pip install *)",
      "Bash(python3 -m pip install *)",
      "Bash(sudo *)",
      "Bash(docker *)",
      "Bash(podman *)"
    ]
  },
  "hooks": {
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/.claude/hooks/startup-claude.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/.claude/hooks/block-secrets-in-bash.sh"
          }
        ]
      }
    ]
  }
}
``model`` sets the default model for sessions in the container.

Users can override it per session.

``sandbox`` example block protects the agent’s own configuration and credentials from modification.

The denyWrite list covers everything that defines the agent’s constraints. The denyRead list blocks access to authentication tokens. autoAllowBashIfSandboxed is false so bash commands still require approval even with the sandbox active.

enableWeakerNestedSandbox activates the container-compatible sandbox mode. Filesystem, network and process isolation still apply; the only limitation is that /proc is inherited from the container rather than mounted fresh.

allowUnsandboxedCommands is false to prevent Claude from retrying failed sandboxed commands without the sandbox.

Sandbox and permissions enforce at different levels.

Deny rules in the permissions block only block Claude’s built-in tools, not bash subprocesses. For example, Read(./.env) blocks the Read tool but cat .env bypasses it. The sandbox denyRead list enforces file access at the OS level regardless of how the file is accessed. Use both layers: permissions for tool gating, sandbox for OS-level enforcement.

``permissions`` example block prevents commands that would modify the container environment.

Package installation, sudo and container runtime commands are denied. Git staging and committing require approval. additionalDirectories extends scope to paths the container uses for configuration and mounted files.

``hooks`` example injects project context at session start, blocks secret access and logs tool calls.

SessionStart outputs the project spec, settings and GPU information into the session. PreToolUse blocks commands that reference secret environment variables from spec.yaml.

Step Three: Create hook scripts.

The SessionStart hook checks whether the container is an AI Workbench project and injects context into the session. It checks the project repository and creates a CLAUDE.md file and .claude subfolder if they don’t exist. It also enters the project spec and settings, and reports GPU availability.

#!/bin/bash

# Check if Claude is in a project container
if [ ! -f /project/.project/spec.yaml ]; then
   echo "Not in a Workbench project container; Ignore Workbench skills"
   exit 0
fi

# Create CLAUDE.md if it doesn't exist
mkdir -p /project/.claude
if [ ! -f /project/CLAUDE.md ]; then
cat << 'EOF' > /project/CLAUDE.md

# Context for this project

## This is an AI Workbench Project

- This is a container
- There are relevant skills in `~/.claude/skills/ai-workbench-container`
- The project repository is at `/project`

EOF

fi

# Output project context
cat << 'EOF'
==============Claude Settings Information===============
This is an AI Workbench Project container.
The project Git repository is located at `/project`.
The project structure can be found in `/project/.project/spec.yaml`.
Use the Workbench skills in `~/.claude/skills/ai-workbench-container`
EOF

echo -e "This is the project spec.yaml file.\n"
cat /project/.project/spec.yaml

echo "These are the settings from ~/.claude/settings.json."
cat ~/.claude/settings.json

# Report GPU availability
if command -v nvidia-smi &>/dev/null && nvidia-smi &> /dev/null; then
   echo -e "\nThere are GPUs available in this container.\n"
   nvidia-smi --query-gpu=index,name,memory.total --format=csv,noheader
fi

The PreToolUse hook blocks bash commands that reference secret environment variables defined in spec.yaml. It parses the project’s spec.yaml for secret variable names and rejects any bash command that references them. It also blocks environment-dumping commands (env, printenv, set, declare -p) when secrets are present.

#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

SPEC="/project/.project/spec.yaml"
if [ ! -f "$SPEC" ]; then
  exit 0
fi

# Extract secret variable names from spec.yaml
SECRETS=$(python3 -c "
import yaml, sys
try:
    spec = yaml.safe_load(open('$SPEC'))
    secrets = spec.get('execution', {}).get('secrets', [])
    for s in secrets:
        print(s['variable'])
except Exception:
    sys.exit(0)
")

if [ -z "$SECRETS" ]; then
  exit 0
fi

# Block env-dumping commands when secrets exist
if echo "$COMMAND" | grep -qxE '\s*(env|printenv|set|declare -p|export -p)\s*'; then
  echo "Blocked: command dumps all environment variables" >&2
  exit 2
fi

if echo "$COMMAND" | grep -qE 'cat\s+/proc/self/environ'; then
  echo "Blocked: reading /proc/self/environ exposes secrets" >&2
  exit 2
fi

# Block commands that reference any secret by name
while IFS= read -r secret; do
  if echo "$COMMAND" | grep -qF "$secret"; then
    echo "Blocked: command references secret variable '$secret'" >&2
    exit 2
  fi
done <<< "$SECRETS"

exit 0
Step Four: Create the SKILL.md file.

The skill tells Claude about the constraints of the project container.

---
name: nvwb-project
description: Apply when working in a Linux environment that has a
  `/project/.project/spec.yaml` file. This skill enforces rules about
  AI Workbench project and container structure, environment configuration,
  and rebuild/restart requirements.
user-invocable: false
---

# NVIDIA AI Workbench — In-Container Project Awareness

## What NOT to Do

- Do NOT use `sudo`
- Do NOT install packages
- Do NOT edit `/project/.project/*` without consulting the user
- Do NOT run `nvwb` commands (Workbench CLI not available here)
- Do NOT print or log any secret environment variables listed in the
  `execution.secrets` section of `/project/.project/spec.yaml`

## What To Do

- Tell the user to rebuild the container after editing:
  `requirements.txt`, `apt.txt`, `postBuild.bash`, `preBuild.bash`
- Tell the user to restart the container after editing:
  `variables.env`, mount or app fields in `spec.yaml`
- Tell the user to restart the Compose application after editing:
  `compose.yaml` or `docker-compose.yml`
- For persistent storage, tell the user to add a mount
  (Claude cannot configure mounts from within the container)
Step Five: Optionally, create a references/config-files.md file.

This gives Claude detailed knowledge about each AI Workbench configuration file. It documents location, format, edit constraints and effect of changes for files like spec.yaml, apt.txt, requirements.txt and build scripts. See the full file in the template repository.

Step Six: Publish the repository.

Push the repository to GitHub or GitLab. It should be accessible from inside the project container during the build.

Include the Configuration in the Build#

Step One: Add the clone to postBuild.bash.

Add the following command after the Claude Code installation commands from Install Claude Code in a Project Container.

# Clone your sandbox configuration to the ~/.claude folder
git clone <url-to-your-config-repo> ~/.claude
Step Two: Build the container.
  1. Select Project Tab > Project Container > Build

Step Three: Verify the configuration is in place.
  1. Open a terminal in the container

  2. Run cat ~/.claude/settings.json to verify the settings file is present

  3. Run claude to start a session and confirm the SessionStart hook outputs project context

Success: The sandbox configuration repository is cloned into the container at build time. Claude Code starts with your settings, hooks and skills in place.

Persisting runtime changes with a volume mount.

The build clones a fresh copy of the configuration every time. If you want runtime changes (such as conversation history) to persist across restarts, set a volume mount for ~/.claude/. Select Project Tab > Project Container > Mounts > Add with Type > Volume Mount and Target Directory set to the configuration directory. Note that volume mount contents take precedence over build output — to reset to the build-time state, remove the volume mount and rebuild.