Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Read-only detection: When /nix/store is read-only, the nix client detects it can’t write directly

  2. Daemon mode: The client automatically switches to daemon mode and communicates via socket

  3. Host builds: The nix daemon on the host performs the actual builds and writes to the store

  4. Instant visibility: Since the container bind-mounts the same store, new paths are immediately visible

  5. 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>