A secure Podman container for running Claude Code against a Nix-managed project.
Each project is mounted at its own path inside the container, and the flake that provides the dev environment can live anywhere — so one shared flake can serve many projects, and each project keeps its own isolated Claude Code session history within a single shared home.
# 1. Copy your Claude Code credentials into the container home.
mkdir -p claude/.claude
cp ~/.claude/.credentials.json claude/.claude/.credentials.json
# 2. Build and load the container image.
nix build
podman load < resultYour account credentials and any global settings (status line, etc.) live in
claude/.claude/ and are shared across every pod started from this clone. To keep
a work account and a personal account separate, clone this repository twice and
copy the appropriate credentials into each clone's claude/.claude/.
1. Generate the dev environment (whenever the flake's inputs change). This now takes two arguments — the flake and the project directory — so the flake need not live inside the project:
# gen-dev-env.sh FLAKE_PATH PROJECT_DIR
./gen-dev-env.sh examples/rust examples/rust # flake and project coincide
./gen-dev-env.sh /path/to/shared-flake /path/to/proj # one shared flake, any projectdev-env.sh is written into PROJECT_DIR, so it appears inside the container at
/work/POD_NAME/dev-env.sh and is sourced automatically by exec.sh.
2. Start the pod, passing the project directory (pod name defaults to the last path component):
./play.sh examples/rust # pod named "rust"
./play.sh /path/to/project # pod named "project"
./play.sh /path/to/project mypod # explicit pod nameThe pod name must be unique if running multiple pods simultaneously. The project
directory is mounted at /work/POD_NAME inside the container (not bare /work),
which gives each project a distinct path — and therefore a distinct Claude Code
session slug.
3. Enter the container (pod name matches the last component of the work directory unless you set one explicitly):
./exec.sh rustThis sources /work/rust/dev-env.sh, changes to /work/rust, and drops you into
an interactive shell with the project's dev environment (PATH, CC, PS1, etc.)
already set. Start Claude Code with claude.
Launch
claudefrom/work/POD_NAME, not a subdirectory. Claude Code keys its session history to the directory it is launched from.exec.shputs you in the right place; if youcdinto a subfolder first and then runclaude, that subfolder becomes a separate session bucket andclaude --resumewill not find the sessions you expect. If you routinely work from a nested root, mount that nested directory as the project (pass it as theplay.shargument) so it becomes/work/POD_NAMEdirectly.
4. Tear down:
podman pod stop rust && podman pod rm rustAll session history lives in the one shared home at
claude/.claude/projects/, so every project's transcripts sit in a single place
you can browse or back up together. Because each project is mounted at a distinct
/work/POD_NAME, Claude Code files its sessions under a distinct slug per project.
The practical effect:
claude --resumelaunched from/work/POD_NAMEonly lists that project's sessions.- One project's Claude session never reads another project's session files.
- The transcripts themselves all reside in the shared home, not scattered across the project directories.
The project's files remain isolated by the mount — a pod can only see its own
/work/POD_NAME, never another project's directory.
Each pod has its own name, its own /work/POD_NAME mount, and its own
dev-env.sh (stored in the project directory). Project files are isolated by the
mount; session history is shared in one home but scoped per project on resume.
./gen-dev-env.sh /path/to/shared-flake examples/rust && ./play.sh examples/rust && ./exec.sh rust
./gen-dev-env.sh /path/to/shared-flake examples/haskell && ./play.sh examples/haskell && ./exec.sh haskellgen-dev-env.sh writes dev-env.sh into each project directory. If a project is
a git repository, add dev-env.sh to its .gitignore (and .claude/ if you do
not want project-local Claude settings or session-adjacent files tracked).
nix print-dev-env evaluates a flake's devShell on the host and emits a shell
script that sets PATH and build variables to the relevant /nix/store/...
paths. Since the container mounts the host Nix store read-only, those paths are
accessible without a daemon socket. The container has no write access to the Nix
store and no daemon socket — it can read derivations but cannot build, delete, or
garbage-collect them. Because the emitted paths are absolute /nix/store paths,
the flake that produced them does not need to be visible inside the container at
all — only the store does.
See CLAUDE.md for architecture details and alternative approaches.