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

lagoon init create lagoon.toml interactively (live nixpkgs search) lagoon shell enter the sandboxed environment lagoon run run a one-off command in the sandbox lagoon up start services from [up] config at localhost ports lagoon docker export environment as a Docker image tar lagoon watch restart a command on file changes lagoon ps show status and running sandbox info lagoon rm remove cached environment for this project lagoon check validate lagoon.toml and verify closure integrity lagoon save export environment to .nar file (offline transfer) lagoon load import environment from .nar file lagoon version print version and pinned nixpkgs commit

Install

lagoon requires Linux (arm64 or amd64), bubblewrap, and Nix.

1. Install dependencies

$ sudo apt install bubblewrap $ sh <(curl -L https://nixos.org/nix/install) --no-daemon $ source ~/.nix-profile/etc/profile.d/nix.sh

2. Install lagoon

$ curl -fsSL https://raw.githubusercontent.com/imraghavojha/lagoon/main/install.sh | bash

Verify it works:

$ lagoon version
On ARM: Some packages may compile from source on first use. Subsequent runs are instant from cache.

Quick Start

1. Create your environment

In your project directory:

$ lagoon init

Follow the TUI to search for packages and set your profile. This creates lagoon.toml. Commit it:

$ git add lagoon.toml && git commit -m "add lagoon environment"

2. Enter the sandbox

$ lagoon shell

You're now in an isolated environment. Your project is at /workspace.

3. Use it

[lagoon] /workspace $ python3 --version Python 3.11.x [lagoon] /workspace $ node --version v20.x.x

4. Exit

[lagoon] /workspace $ 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:

  1. lagoon reads lagoon.toml and generates shell.nix
  2. nix-shell resolves packages (cached on subsequent runs)
  3. bwrap creates an empty sandbox with only:
    • Your project at /workspace
    • Nix store (read-only)
    • Ephemeral home directory
    • Minimal /etc for networking & SSL
  4. You get a bash shell inside the sandbox
  5. On exit, everything except /workspace is gone
No daemon: Each 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.

No drift: The same 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.

$ lagoon init
  • 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.toml exists, lagoon asks before overwriting.
$ git add lagoon.toml && git commit -m "add lagoon environment"

lagoon shell

Enters the sandboxed environment defined in lagoon.toml.

$ lagoon shell

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 also lagoon 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.

$ lagoon run python3 --version Python 3.11.x $ lagoon run make test

Supports the same -e and -m flags as lagoon shell.

CI usage: Use 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.

$ lagoon up ! services run with network access enabled web | node server.js listening on :3000 api | python3 manage.py runserver 0.0.0.0:8000

Configuration

Add an [up] section to lagoon.toml:

packages = ["python3", "nodejs"] profile = "minimal" # services started by lagoon up [up] web = "node server.js" api = "python3 manage.py runserver 0.0.0.0:8000"
  • 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.
Network note: 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.

$ lagoon docker > myimage.tar -> building docker image lagoon-myproject:latest (first run may be slow)... ✓ built /nix/store/xxx-lagoon-myproject.tar.gz $ docker load < myimage.tar $ docker run -it lagoon-myproject:latest

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 buildLayeredImage for 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.
Use case: Build your reproducible environment on a Pi or CI server, then ship the image to any container registry or runtime — without ever running Docker during the build.

lagoon watch

Live reload for your sandbox. Watches the current directory for file changes and automatically restarts your command.

$ lagoon watch "python3 server.py"
  • 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 shell once first to build the environment.

lagoon ps

Shows the current project's environment status and any running sandbox processes.

$ lagoon ps project myproject packages python3 nodejs profile minimal cached yes PID MEMORY PROJECT PACKAGES 12345 42.1 MB myproject python3 nodejs
  • 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).
No config? 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.

$ lagoon rm

Use this to force a rebuild. The next lagoon shell will regenerate everything from scratch.

Cache location: ~/.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.

$ lagoon check
  • 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 check --reset

lagoon save

Snapshots your environment for air-gapped deployments or offline transfer.

$ lagoon save > environment.nar -> exporting 47 store paths...

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.

$ lagoon load environment.nar -> importing from environment.nar...

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 version lagoon v0.3 nixpkgs commit 26eaeac4e409d7b5a6bf6f90a2a2dc223c78d915

lagoon.toml Format Reference

lagoon reads a single config file: lagoon.toml in your project root.

Full example

# lagoon.toml — commit this file packages = ["python3", "nodejs", "ffmpeg"] # pinned nixpkgs commit — same environment in 2027 nixpkgs_commit = "26eaeac4e409d7b5a6bf6f90a2a2dc223c78d915" nixpkgs_sha256 = "1knl8dcr5ip70a2vbky3q844212crwrvybyw2nhfmgm1mvqry963" # "minimal" = no network | "network" = host network profile = "minimal" # optional: runs on every shell entry on_enter = "source .venv/bin/activate" # optional: services for lagoon up [up] web = "node server.js" api = "python3 manage.py runserver 0.0.0.0:8000"

Minimal example

packages = ["cowsay"] nixpkgs_commit = "26eaeac4e409d7b5a6bf6f90a2a2dc223c78d915" nixpkgs_sha256 = "1knl8dcr5ip70a2vbky3q844212crwrvybyw2nhfmgm1mvqry963" profile = "minimal"

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.

Don't edit commit/sha256 manually. Run 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 3
  • nodejs — Node.js
  • postgresql — PostgreSQL database
  • rustup — Rust toolchain manager
  • gcc — C/C++ compiler
  • git — Version control
  • go — Go compiler
  • ffmpeg — Audio/video processing
Typos are caught: 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:

$ lagoon run make test

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 packages list
  • Set profile = "network" if needed
  • Run lagoon rm then lagoon shell to 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 rm to force a fresh build.
  • Closure changed: Run lagoon check --reset to re-establish the baseline after intentional updates.

Have a question? Open an issue on GitHub.