lagoon Docs
Reproducible sandboxed shell environments. No Docker. No root. No daemons.
lagoon creates isolated shell environments with exactly the tools you need. Commit one config file and everyone on your team — plus that Raspberry Pi — gets an identical environment.
It uses bwrap (bubblewrap) for isolation and nix for reproducibility. First run builds the environment. Next runs start instantly.
Command overview
Install
lagoon requires Linux (arm64 or amd64), bubblewrap, and Nix.
1. Install dependencies
2. Install lagoon
Verify it works:
Quick Start
1. Create your environment
In your project directory:
Follow the TUI to search for packages and set your profile. This creates lagoon.toml. Commit it:
2. Enter the sandbox
You're now in an isolated environment. Your project is at /workspace.
3. Use it
4. Exit
Back on your host shell. Nothing persists from inside the sandbox.
How It Works
lagoon combines three core technologies:
Nix Package Manager
Resolves exact package versions from a pinned nixpkgs commit. Same packages, same versions, everywhere.
Bubblewrap Sandbox
Uses Linux user namespaces to create an isolated filesystem. No root required. No daemon.
Go Backend
A fast, compiled core that manages the TUI, file watching, and sandbox lifecycle with zero overhead.
When you run lagoon shell:
- lagoon reads
lagoon.tomland generatesshell.nix - nix-shell resolves packages (cached on subsequent runs)
- bwrap creates an empty sandbox with only:
- Your project at
/workspace - Nix store (read-only)
- Ephemeral home directory
- Minimal
/etcfor networking & SSL
- Your project at
- You get a bash shell inside the sandbox
- On exit, everything except
/workspaceis gone
lagoon shell is independent. Exit and nothing persists.
Isolation
Your sandbox is isolated by default. Here's what's blocked:
Network
Off unless you set profile = "network". Prevents accidental data leaks.
Host Filesystem
Only /workspace (your project) is visible. /home, /root, /etc/shadow are invisible.
Environment
Host $PATH, $PYTHONPATH, tool configs — all wiped. Only nix packages are on PATH.
Home Directory
Ephemeral tmpfs. Write config files if you need to — they vanish on exit.
To enable network: Set profile = "network" in lagoon.toml.
Reproducibility
One lagoon.toml file guarantees identical environments everywhere.
Each lagoon binary ships with a pinned nixpkgs commit.
When you run lagoon init, it bakes that pin into lagoon.toml.
Anyone — your teammate, your CI, a Raspberry Pi — who runs lagoon shell gets the exact same package versions.
lagoon.toml in 2027 gives you the same environment as today.
To update packages: Edit lagoon.toml directly and change the packages list.
To update the nixpkgs pin, run lagoon init again — it will prompt to overwrite.
lagoon init
Interactive setup for your project. Launches a live nixpkgs search TUI.
- Live Search: Queries search.nixos.org as you type with debounced results.
- Interactive Selection: Use arrow keys to pick packages and toggle network access.
- Preview & Confirm: Shows exactly what will be written before saving.
- Overwrite guard: If
lagoon.tomlexists, lagoon asks before overwriting.
lagoon shell
Enters the sandboxed environment defined in lagoon.toml.
First run builds the environment (may take minutes on ARM). Subsequent runs are instant (from cache).
Inside the sandbox
/workspace— Your project directory (read/write)$HOME—/home(ephemeral)$PATH— Only nix packages you requested- Network — Off by default (unless
profile = "network")
Flags
--cmd— Run a one-off command and exit (see alsolagoon run).--env,-e— Inject environment variables (e.g.,-e DEBUG=1). Can be repeated.--memory,-m— Cap sandbox memory via systemd-run (e.g.,-m 512m). Requires systemd.
lagoon run
Runs a one-off command in the sandbox and exits. Shorthand for lagoon shell --cmd.
Supports the same -e and -m flags as lagoon shell.
lagoon run in scripts and pipelines where you don't need an interactive shell.
lagoon up
Starts all services defined in the [up] section of lagoon.toml, each inside the sandbox,
with their ports accessible at real localhost addresses — like docker-compose up.
Configuration
Add an [up] section to lagoon.toml:
- Multiplexed output: Each service gets a colored prefix so logs don't mix.
- Real localhost ports: Services always run with network access, so they bind to your host's loopback interface.
- Graceful shutdown: Ctrl+C sends SIGTERM to all services, then SIGKILL after 500ms.
- Sorted start order: Services start alphabetically for deterministic output.
lagoon up always enables network access regardless of your profile setting.
Services need to bind to localhost ports. lagoon prints a note about this at startup.
lagoon docker
Exports your environment as a Docker-loadable image tar. No Docker daemon required to build it.
How it works
lagoon generates a docker.nix expression using nixpkgs.dockerTools.buildLayeredImage —
nix's native Docker image builder — then runs nix-build to produce a layered OCI image.
The result streams to stdout, so redirect it to a file.
- Image name: Derived from your project directory — e.g.,
lagoon-myproject:latest. - Layered: Uses
buildLayeredImagefor optimal layer caching in Docker. - No daemon: Built entirely with nix — Docker doesn't need to be running on the build machine.
- stdout-safe: Refuses to write binary to a terminal; you must redirect to a file.
lagoon watch
Live reload for your sandbox. Watches the current directory for file changes and automatically restarts your command.
- Debounce: 300ms delay prevents rapid-fire restarts while saving multiple files.
- Sandbox mode: The command runs inside the same isolated environment as
lagoon shell. - Warm cache required: Run
lagoon shellonce first to build the environment.
lagoon ps
Shows the current project's environment status and any running sandbox processes.
- Project block: packages, profile, and whether the environment is cached and ready.
- Process table: PIDs of any running sandbox processes with memory usage (VmRSS from
/proc).
lagoon ps exits 0 and suggests running lagoon init. It's informational, never a hard gate.
lagoon rm
Removes the cached environment for the current project.
Use this to force a rebuild. The next lagoon shell will regenerate everything from scratch.
~/.cache/lagoon/. Each project gets a hash-based subdirectory.
lagoon rm only removes the current project's directory — other projects are untouched.
lagoon check
Validates the structure and packages of your lagoon.toml and verifies cryptographic closure integrity.
- Schema: Ensures packages, commit, and sha256 are present and correctly formatted.
- No duplicates: Catches duplicate package names before any expensive nix operations.
- Package existence: Queries nixpkgs live to verify every package actually exists.
- Offline mode: If no internet is detected, skips existence checks and warns instead of failing.
- Closure fingerprint: SHA256 of all nix store paths. First run sets a baseline; subsequent runs compare against it.
Flags
--reset— Wipe the closure baseline and re-establish it from the current environment. Use this after intentionally updating packages.
lagoon save
Snapshots your environment for air-gapped deployments or offline transfer.
Dumps the entire environment closure to a binary .nar file using nix-store --export.
Requires a warm cache — run lagoon shell first.
Use cases: Raspberry Pis behind firewalls, secure CI environments, or lab machines with no external connectivity.
lagoon load
Restores an exported closure on a machine without internet access.
Calls nix-store --import under the hood.
After loading, run lagoon shell normally — the environment will be available instantly from cache.
lagoon version
Prints the lagoon version and the pinned nixpkgs commit baked into the binary.
lagoon.toml Format Reference
lagoon reads a single config file: lagoon.toml in your project root.
Full example
Minimal example
Options
packages
Required. List of nix package names. Space-separated in lagoon init, array in TOML.
nixpkgs_commit
Required. 40-character git commit hash of nixpkgs-unstable. Auto-set by lagoon init.
nixpkgs_sha256
Required. 52-character SHA256 of the nixpkgs tarball. Auto-set by lagoon init.
profile
Optional. Either "minimal" (no network) or "network" (host network enabled). Default: "minimal".
on_enter
Optional. A shell command run every time the sandbox starts — before handing control to bash or before --cmd executes. Example: "source .venv/bin/activate".
[up]
Optional. A TOML table mapping service names to shell commands. Used by lagoon up. Each value is a full shell command run inside the sandbox with network access enabled.
lagoon init to update the nixpkgs pin safely.
Finding Packages
Package names come from the Nix package collection.
Search for packages
Visit search.nixos.org/packages and search by name.
lagoon's init TUI also searches it live as you type.
Examples:
python3— Python 3nodejs— Node.jspostgresql— PostgreSQL databaserustup— Rust toolchain managergcc— C/C++ compilergit— Version controlgo— Go compilerffmpeg— Audio/video processing
lagoon check queries nixpkgs live to verify every package in your list. Misspelled names show a clean error with a link to the search.
FAQ
Why is first run slow on my Raspberry Pi?
Some packages aren't in the binary cache for ARM and compile from source. After that, runs are instant from cache.
How do I use lagoon in CI/CD?
Commit lagoon.toml to your repo. In CI, install lagoon, then:
How do I run full-stack services?
Add an [up] section to lagoon.toml and run lagoon up.
Each service runs inside the sandbox and is accessible at localhost ports.
See the lagoon up docs.
Can I export my environment as a Docker image?
Yes — lagoon docker > myimage.tar builds a layered OCI image from your nix environment.
Load it with docker load < myimage.tar.
See the lagoon docker docs.
Can I add persistent home directory files?
No. Home is ephemeral (tmpfs).
Project files go in /workspace (your project directory) and persist.
Does lagoon work on macOS?
No. lagoon requires Linux (arm64 or amd64). It uses Linux-specific tools: bubblewrap and user namespaces.
Can I modify packages after lagoon init?
Yes. Edit lagoon.toml directly:
- Change the
packageslist - Set
profile = "network"if needed - Run
lagoon rmthenlagoon shellto pick up the changes
What's the difference between lagoon shell and Docker?
lagoon
- No daemon
- No root required
- Instant warm starts (<300ms on Pi 4)
- Reproducible via Nix
- Can export to Docker
- Linux only
Docker
- Daemon required
- Needs root or docker group
- Slower starts (15s+)
- Image updates drift
- Cross-platform
Can I use lagoon on a Raspberry Pi?
Yes — that's the primary target. Install bubblewrap and nix, then curl ... | bash to install lagoon.
What if preflight checks fail?
- bubblewrap: Install with
sudo apt install bubblewrap - nix: Install with
sh <(curl -L https://nixos.org/nix/install) --no-daemon - user namespaces: Check
/proc/sys/kernel/unprivileged_userns_clone— should be 1 or absent
Where is the cache stored?
Nix stores packages in /nix/store (system-wide, shared).
lagoon caches resolved environments in ~/.cache/lagoon/.
Run lagoon rm to wipe the current project's cache.
How do I troubleshoot?
lagoon prints the underlying error from nix-shell or bwrap below a clear separator.
Common fixes:
- Package not found: Verify spelling at search.nixos.org or run
lagoon check. - Network errors: Ensure internet access for the first build.
- Stale cache: Run
lagoon rmto force a fresh build. - Closure changed: Run
lagoon check --resetto re-establish the baseline after intentional updates.
Have a question? Open an issue on GitHub.