This project allows you to run Claude Code, configured to use models via Vertex, inside a Podman container with SELinux isolation. This means that only the files on your current project are available to Claude; it has no ability to either see other files or modify your system.
In addition, Claude is configured to use its own sandbox, which blocks the Bash tool from writing to files outside of your project, in exchange for not prompting you to approve each command. (Note that in my experience you must also be in "accept edits" mode to skip the prompts. Press Shift-Tab to cycle between modes.) It also has a network proxy that allows it to control any network access by the Bash tool.
Many commentators have observed that running Claude Code with
--dangerously-skip-permissions
is a qualitatively different
experience
to running without. When you don't have to constantly approve each command to
make progress, you can set Claude to work on a problem and go do something
else. (Often the task could be something like fixing a broken unit test, which
requires many experiments but not necessarily large-scale code changes.)
Unfortunately, as the name suggests, this is dangerous. There are many ways to give the LLM access to untrusted input (e.g. random review comments on a public repository), and it will be only too happy to follow any instructions therein. Meanwhile, it has the ability to write arbitary code and run it, with full access to the environment in which it is running. And, in fact, this is largely true even when you don't skip permissions: if you have given Claude permission to run your tests, and it is also in a mode where it can write code, then it can run arbitrary code in your environment, possibly at the instruction of an untrusted attacker.
Claude Code now has its own sandbox
mode, but
unfortunately it is not very helpful for this problem. It is focused on
preventing network egress to avoid exfiltration of data, and as such it only
prevents modifying files outside of the project (to avoid circumventing the
network proxy), not reading them. Thus it can prevent the LLM running rm -rf ~, but not reading
your SSH private key and posting it on some site that you have previously given
permission for (e.g. GitHub). Furthermore, if any command fails as a result of
the sandboxing, the LLM can and will simply retry it outside of the
sandbox.
Running inside a container means that Claude Code can only see and modify data that is explicitly mounted into the container environment. Using Podman means the container can be run without root permissions, reducing the danger of privilege escalation, and using SELinux means that the isolation is enforced by mandatory access control. Within the container, you can allow Claude to install new packages without affecting your system.
If your personal risk calculus allows it, you may choose to run with
--dangerously-skip-permissions inside the container. However, the sandbox
mode is also enabled and this has the effect of reducing the permission
requests that you generally would not want to see (allowing Claude to grind
away in the background with sandboxed commands in accept-edits mode) without
eliminating all permissions checks.
First, you must log in to GCloud to provide access to Vertex by doing:
gcloud auth application-default login
gcloud auth application-default set-quota-project ...
The easiest way to run Claude is to add an alias in your ~/.bashrc file:
alias claude="path/to/claude-container/claude"
alias yolo="path/to/claude-container/claude --dangerously-skip-permissions"
Then just run claude in your project directory (from a new shell). The first
time will take a while as it builds the container image locally.
Auto-updating is disabled. To update, rebuild the container image by running
build-claude.sh --rebuild. This restarts the image build with the latest
version of Fedora and its packages as well as Claude Code.
To allow Claude to use the gh CLI command, create a fine-grained personal access token and store it in the Podman Secret claude-github-token by copying it to the clipboard and doing:
podman secret create --replace claude-github-token <(wl-paste)
Example permissions:
- Read public repos only
- Read public and private repos (including Issues and PRs)
- Write to repos, PRs, and Issues
To pass through all of your GitHub permissions (not recommended!), you can do:
gh auth token | podman secret create --replace claude-github-token
To allow Claude to query your Gmail, Calendar, Drive, and Tasks (read-only), install the Google Workspace CLI on your host and set up credentials:
npm install -g @googleworkspace/cli
gws auth setup
gws auth login
During gws auth login, select only read-only scopes for each service:
gmail.readonly, calendar.readonly, drive.readonly, tasks.readonly.
The container mounts ~/.config/gws/ read-only and sets
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file so gws can decrypt
credentials using the file-based encryption key.
To test inside the container:
gws gmail users getProfile --params '{"userId": "me"}'
gws calendar events list --params '{"calendarId": "primary", "singleEvents": true, "orderBy": "startTime"}'
To allow Claude to manage Jira issues, first set up jira-cli on your host:
go install github.com/ankitpokhrel/jira-cli/cmd/jira@latest
export JIRA_API_TOKEN=YOUR_TOKEN # from https://id.atlassian.com/manage-profile/security/api-tokens
jira init
jira init is interactive — it queries your Jira instance for issue types,
boards, and custom fields, then writes a full config to
~/.config/.jira/.config.yml. This step cannot be done inside the
container (the TUI board picker requires interactive input).
Once initialized, create the Podman secrets:
podman secret create --replace jira-api-token <(echo -n 'YOUR_TOKEN')
podman secret create --replace jira-config ~/.config/.jira/.config.yml
The container mounts the config at ~/.config/.jira/.config.yml and injects
JIRA_API_TOKEN as an environment variable.
To test inside the container:
jira issue list -q 'assignee = currentUser() AND status not in (Closed)' --project YOURPROJECT
It is currently not possible to run a nested Podman container inside the container. Unfortunately getting this working would require disabling SELinux labelling on the outer container.
Other capabilities may be disabled or packages missing simply because I haven't encountered a need for them yet. Pull requests are welcome.