Nix Store Sharing
Forage sandboxes share the host’s nix store, avoiding duplication while maintaining isolation.
How It Works
The nix store is bind-mounted read-only into each container:
bindMounts = {
"/nix/store" = {
hostPath = "/nix/store";
isReadOnly = true;
};
};
When an agent needs to install packages, they go through the host’s nix daemon:
┌─────────────────────────────────────────────────────────────────┐
│ Host │
│ │
│ nix-daemon ◄──────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ /nix/store ◄──────────────────────────────┼───────────┐ │
│ (writable by daemon) │ │ │
│ │ │ │
│ ┌─────────────────────────────┐ ┌────────┴───────────┴──┐ │
│ │ sandbox-a │ │ sandbox-b │ │
│ │ │ │ │ │
│ │ /nix/store (read-only) │ │ /nix/store (read-only)│ │
│ │ │ │ │ │
│ │ $ nix run nixpkgs#ripgrep │ │ $ nix shell nixpkgs#jq│ │
│ │ │ │ │ │ │ │
│ │ └─────────────────────┼──┼───────┘ │ │
│ │ │ │ │ │
│ └─────────────────────────────┘ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Why This Works
-
Read-only detection: When
/nix/storeis read-only, the nix client detects it can’t write directly -
Daemon mode: The client automatically switches to daemon mode and communicates via socket
-
Host builds: The nix daemon on the host performs the actual builds and writes to the store
-
Instant visibility: Since the container bind-mounts the same store, new paths are immediately visible
-
Content-addressed: Nix’s content-addressed store means there are no conflicts—the same input always produces the same output path
Benefits
No Duplication
Without store sharing, each container would need its own copy of:
- Base system packages
- Development tools
- Agent binaries
With sharing, the store is used efficiently:
Without sharing:
Container A: /nix/store/...-ripgrep-14.0.0 (15MB)
Container B: /nix/store/...-ripgrep-14.0.0 (15MB)
Container C: /nix/store/...-ripgrep-14.0.0 (15MB)
Total: 45MB
With sharing:
Host: /nix/store/...-ripgrep-14.0.0 (15MB)
Container A, B, C: bind mount (0MB additional)
Total: 15MB
Instant Availability
Packages already in the host store are immediately available:
# Inside container - if ripgrep is already on host
$ nix run nixpkgs#ripgrep -- --version
ripgrep 14.0.0
# (instant, no download/build)
Shared Build Cache
If one container builds a package, others can use it:
# Container A builds a package
$ nix build nixpkgs#somePackage
# Container B can use it immediately (same store path)
$ nix run nixpkgs#somePackage
# (no rebuild needed)
Using Nix in Sandboxes
One-Off Commands
# Run a tool without installing
nix run nixpkgs#ripgrep -- --help
nix run nixpkgs#jq -- '.foo' data.json
Interactive Shell
# Enter a shell with multiple tools
nix shell nixpkgs#nodejs nixpkgs#yarn nixpkgs#typescript
# Now node, yarn, tsc are available
node --version
Building Projects
# Build a flake-based project
cd /workspace
nix build
# Run the result
./result/bin/myapp
Development Shells
# Enter a project's dev shell
cd /workspace
nix develop
# Or with direnv (if project has .envrc)
direnv allow
Limitations
No Direct Store Writes
Containers cannot write directly to /nix/store:
# This won't work
$ nix-store --add myfile
error: cannot open `/nix/store/.../myfile' for writing: Read-only file system
All writes must go through the daemon.
Daemon Socket Required
The nix daemon socket must be accessible. This is handled by systemd-nspawn’s socket activation.
Store Garbage Collection
Garbage collection happens on the host. If the host runs nix-collect-garbage, it may remove paths that containers are using.
Best practice: Don’t run aggressive garbage collection while sandboxes are active.
Registry Pinning
Forage automatically pins the nix registry in each sandbox to match the host’s nixpkgs version. This ensures consistency across all nix run nixpkgs#foo and nix shell commands.
How It Works
The host module extracts the nixpkgs revision from its flake inputs and passes it to each container. The container’s /etc/nix/registry.json is configured to resolve nixpkgs to this specific revision:
{
"version": 2,
"flakes": [{
"from": { "type": "indirect", "id": "nixpkgs" },
"to": {
"type": "github",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "abc123..."
}
}]
}
Benefits
- Consistency: All sandboxes use the same nixpkgs version
- No store bloat: Packages aren’t duplicated across nixpkgs versions
- Reproducibility: Tool installations are reproducible across sandboxes
- Cache efficiency: If the host already has a package, it’s instantly available
Verification
Inside a sandbox, you can verify the pinning:
# Show the registry
nix registry list
# The nixpkgs entry should show the pinned revision
# nixpkgs flake:nixpkgs github:NixOS/nixpkgs/<rev>