Hermetic by Default: Why I Split My Fedora Atomic Workspace into Two Containers
Why I switched to Fedora Atomic #
I recently moved my main machine to Fedora Atomic (Silverblue). The main reason? As hermetic a work environment as possible. Long before the switch I already ran applications and commands in containers whenever I could — the Google Photos Takeout migration is a good example, where I ran a random GitHub script inside a Docker container and nobody knows why…
On an immutable host this stops being a nice habit and becomes the natural way of working. You can’t just dnf install whatever you want into the system — well, you can with rpm-ostree layering, but every layered package is a small crack in the immutability. Containers, on the other hand, are the intended unit of change here.
1. Toolbox dev — my persistent workspace
#
The first building block is a named toolbox container called dev. Creating one is trivial: toolbox create -y dev spins up a Fedora container that shares your home directory and user identity with the host. Then you install whatever the environment needs inside the container, for example toolbox run -c dev sudo dnf install -y make golang hugo, and you can drop into it interactively with toolbox enter dev. My dotfiles automate exactly this, so the container is reproducible on any fresh Atomic install.
Because the container is named and persistent, it keeps its state: installed packages, configuration, shell history. It survives reboots and it is mine — I customize it freely.
On top of that, my ~/.bashrc.d/toolbox defines shell wrappers so the tools feel native: typing make, hugo or go in any shell on the host actually executes toolbox run --container dev ... under the hood. I never have to think about which container I’m in — the command just runs in the right place.
# ~/.bashrc.d/toolbox
make(){
toolbox run --container dev /usr/bin/make "$@"
}
hugo() {
toolbox run --container dev hugo "$@"
}
go() {
toolbox run --container dev go "$@"
}2. Sandbox — a sealed room for untrusted commands #
The second building block is the opposite: an ephemeral sandbox. My ~/.bashrc.d/sandbox defines a sandbox function that runs any command inside a fresh podman container with --rm, so the container is deleted the moment it exits. It mounts only the current working directory (with the SELinux :Z label) and runs with --userns=keep-id, so files it creates still belong to me. Nothing else from the host is visible, and nothing persists.
This is where random scripts from the internet, sketchy CLIs and one-off tools go: sandbox some-random-tool. Even if the tool is malicious or just broken, the worst case is a deleted container and a mess in the current directory — not a compromised host.
# ~/.bashrc.d/sandbox
sandbox() {
podman run --rm -it \
--userns=keep-id \
-v "$PWD":/work:Z \
-w /work \
"registry.fedoraproject.org/fedora-toolbox:$(rpm -E %fedora)" \
"$@"
}3. AI agents get their own container too #
The same logic applies to AI agents. I run Mistral Vibe — the agent I’m using right now — inside its own dedicated toolbox container called vibe, separate from dev. An agent can read files, run commands and install things, so it gets the same treatment as any other tool I don’t fully control: it lives in a container, with only what it needs visible, and the host stays untouched. The vibe wrapper in my bashrc routes every invocation into that container automatically.
# ~/.bashrc.d/toolbox
vibe(){
toolbox run --container vibe /home/pawel/.local/bin/vibe "$@"
}The difference at a glance #
dev toolbox | vibe toolbox | sandbox | |
|---|---|---|---|
| Lifetime | persistent, named | persistent, named | ephemeral, --rm |
| Purpose | my trusted daily tools | my AI agent | untrusted / one-off commands |
| State | kept between runs | kept between runs | wiped on exit |
| Filesystem | home dir shared with host | only what it needs | only $PWD mounted, SELinux :Z |
| Identity | normal user | normal user | --userns=keep-id so created files are mine |
Why the separation matters #
On Fedora Atomic this is exactly the mental model the system pushes you toward: the host stays pristine, and everything else lives in containers. Keeping three flavors of “container” — persistent dev for work, persistent vibe for the agent, disposable sandbox for everything else — makes the trust boundary obvious in daily use. One word in the shell decides how much trust a command gets: make ... runs in my workspace, vibe ... in the agent’s container, sandbox ... in a sealed room.
That’s the whole setup: hermetic by default, trusted only by explicit choice. And it’s the aliases in ~/.bashrc.d that tie it all together into one coherent whole at the system level — from the user’s perspective there is just make, vibe and sandbox, and each of them transparently lands in the right container.