The need shows up in the most ordinary way. You have a work GitHub account and a personal one, and the gh CLI stores its auth in one place. You want the stable release and the beta of the same tool installed side by side, and the second install overwrites the first. You have two Discord accounts, and the desktop app keeps exactly one login. Each of these is a five-minute annoyance that recurs forever, and the standard fix is to reach for a container, which means writing a Compose file and hand-wiring volumes to run what is fundamentally a single binary.
Warren takes the position that most of what a container provides is overhead for this job, and that the isolation people actually need is a private home directory and a rewritten path. Repo: swadhinbiswas/warren
Two accounts, one tool
The everyday use is deliberately small.
warren dig gh --as gh-work
warren dig gh --as gh-personal
gh-work # logs into your company GitHub
gh-personal # logs into your personal GitHub
Both instances behave independently. They share the host kernel and nothing else. warren ls shows what is installed, and warren inspect shows where an instance actually lives on disk.
The same idea covers old and new versions. You can install a tool into two aliases and upgrade one without touching the other, which turns "I want to try the beta without breaking my setup" from a virtual machine exercise into a command.
Three levers, no namespaces
The mechanism is worth explaining because it is simpler than the outcome suggests. There are no kernel namespaces and no OverlayFS. Isolation comes from three moves.
The first is intelligent rewriting. When you install a tool through a shell script, Warren intercepts the script and scans it for hardcoded paths like /usr/local/bin or ~/.config, then rewrites them to point inside the instance's private directory under ~/.warren/instances/<alias>. A tool that thinks it is writing to your home directory is writing to its own world.
The second is environment injection. Warren generates a small wrapper script in ~/.local/bin. When you run the alias, the wrapper overrides $HOME, $XDG_CONFIG_HOME, $XDG_DATA_HOME, and $TMPDIR. Graphical instances additionally keep the host display and session bus, so $DISPLAY, $WAYLAND_DISPLAY, $DBUS_SESSION_BUS_ADDRESS, and the runtime dir still point at the real session. That last detail is what keeps a wrapped app from breaking Wayland, notifications, and audio.
The third is execution. The target boots up believing its home directory is real, and the wrapper stays out of the way after the environment is set.
For apps with no installer to rewrite, there is app wrapping. Warren resolves the host launch command once, whether that is flatpak run or a path under /usr/bin, and bakes it into the per-instance launcher. Same sandbox, zero copies of the binary.
<svg viewBox="0 0 900 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="One host binary fanning out into isolated instances">
<rect x="360" y="30" width="180" height="52" rx="12" fill="none" stroke="currentColor" stroke-opacity="0.6" stroke-width="2"/>
<text x="450" y="62" text-anchor="middle" font-family="ui-monospace, monospace" font-size="14" fill="currentColor">gh (shared)</text>
<g fill="none" stroke="currentColor" stroke-opacity="0.35" stroke-width="2">
<path d="M450,82 V120 H180 V170"/>
<path d="M450,82 V120 H450 V170"/>
<path d="M450,82 V120 H720 V170"/>
</g>
<g fill="none" stroke="#fab387" stroke-opacity="0.8" stroke-width="2">
<rect x="90" y="170" width="180" height="90" rx="12"/>
<rect x="360" y="170" width="180" height="90" rx="12"/>
<rect x="630" y="170" width="180" height="90" rx="12"/>
</g>
<g font-family="ui-monospace, monospace" font-size="13" fill="currentColor" opacity="0.7">
<text x="180" y="206" text-anchor="middle">home/</text>
<text x="180" y="228" text-anchor="middle">config/</text>
<text x="450" y="206" text-anchor="middle">home/</text>
<text x="450" y="228" text-anchor="middle">config/</text>
<text x="720" y="206" text-anchor="middle">home/</text>
<text x="720" y="228" text-anchor="middle">config/</text>
</g>
<circle r="6" fill="#fab387">
<animateMotion dur="2.6s" repeatCount="indefinite" path="M450,82 V120 H180 V170"/>
</circle>
<circle r="6" fill="#fab387">
<animateMotion dur="2.6s" begin="0.6s" repeatCount="indefinite" path="M450,82 V120 H450 V170"/>
</circle>
<circle r="6" fill="#fab387">
<animateMotion dur="2.6s" begin="1.2s" repeatCount="indefinite" path="M450,82 V120 H720 V170"/>
</circle>
</svg>
GUI apps, wrapped instead of reinstalled
The extension that made Warren more useful than I expected was wrapping apps you already have. Prefixes like flatpak:, snap:, apt:, dnf:, pacman:, app:, and desktop: resolve to an installed binary, and each alias gets a private sandbox plus its own .desktop entry.
warren dig flatpak:com.discordapp.Discord --as discord-work
warren dig flatpak:com.discordapp.Discord --as discord-home
warren dig apt:firefox --as firefox-work
warren dig app:mytool --as mytool-gui --gui
Each instance appears in the app grid, keeps the host audio and session working, and launches detached. Because the host binary is reused, a second instance costs almost no disk, which is the opposite of what you get from running another copy of an Electron app. warren ls reports whether an instance is a CLI or a GUI, and warren rm removes the desktop entry along with the data.
There is a real limit here, and it is worth stating. Wrapping gives an app its own data and login. It does not give it its own kernel, and it is not a security boundary against hostile software. Warren is explicit that it is not a container runtime, not a VM, and not a privilege escalation tool, and it will refuse to run as root. That honesty is what lets the rest of it stay small.
Sessions, because desktops crash
The feature I did not plan and now use constantly is session snapshots. A reboot, a crash, or a closed window with twelve tabs is a small disaster, and Warren can capture the running desktop and bring it back.
warren session save
warren session restore
warren session restore --dry-run
warren session prune
Snapshots are tiny TOML files under ~/.warren/sessions/, with a retention limit that defaults to keeping the last five and deletes older ones on save. The dry-run flag prints the relaunch plan before touching anything, which is the difference between a useful feature and a terrifying one.
The shape it ended up in
Warren is a single Rust binary, rootless, with no daemons and under fifty milliseconds of startup overhead. It detects Bash, Zsh, Fish, or Nushell and configures the path itself. Configuration lives in ~/.warren/config.toml, covering things like keeping the last five session snapshots, and standard environment variables such as NO_COLOR are respected. Installation is a one-liner that downloads a prebuilt binary when one exists for your architecture and falls back to cargo install when it does not.
The design lesson is that the heavier tool is not always the more isolated one. When the actual requirement is "this process should not see my other account's data", a private home directory, a path rewrite, and an environment override get you there without a kernel feature, a daemon, or a container runtime. The rest of the container is solving problems this case does not have.
The install script and the full command reference are in the repository above.
Reach me at swadhinbiswas.cse@gmail.com or on GitHub and LinkedIn.
No comments yet.