Where things go
The system is one image that updates as a whole. Software goes in one of four places, by what it is.
Where does it go?
| You want | It goes in | How |
|---|---|---|
| An app with a window | Flatpak | GNOME Software, or flatpak install --user |
| A compiler, SDK, language runtime | A toolbox | toolbox create, then dnf inside it |
| A per-project toolchain | mise and direnv | on the host already, installing into $HOME |
| A server or background service | A quadlet | pulsar setup quadlet |
| A local model, anything CUDA | A GPU container | podman run --device nvidia.com/gpu=all |
| A kernel module, udev rule, host daemon | Layered on the host | sudo rpm-ostree install, as a last resort |
Apps: Flatpak
Desktop apps come as Flatpaks. GNOME Software can reach all of Flathub, unfiltered, plus Fedora’s own collection. First boot installs Steam, Heroic, Faugus, Bottles, ProtonPlus, protontricks, OBS Studio, Discord, EasyEffects, virt-manager, Flatseal, the Showtime video player, the Decibels music player and the GNOME set. An app you remove stays removed.
flatpak install --user flathub org.gimp.GIMP # yours, no password
flatpak list --app # what you have
sudo pulsar setup apps # put back any default app you removed
--user installs for your account only and needs no password. A system install
(GNOME Software’s default) is shared by all accounts.
Dev tools: a toolbox
Compilers, SDKs and runtimes go in a toolbox, a Fedora container that shares your home folder.
sudo dnf install inside it changes only the box.
toolbox create # a Fedora box matching this release
toolbox enter # a shell inside it
sudo dnf install rust cargo # inside the box: changes the box, not the OS
toolbox run cargo build # or run one command from the host
A ready box with git, gcc, cmake, gdb, make, ripgrep, jq and Python headers:
pulsar setup devbox # the default dev box, pulsar-dev, via distrobox
distrobox enter pulsar-dev
It is a distrobox, so toolbox list doesn’t show it. The recipe is
/usr/share/pulsar/distrobox.ini.
For per-project toolchains, mise and direnv are on the host and install
into $HOME:
cd ~/code/app
mise use node@22 python@3.13 # pinned for this project, installed under $HOME
bpftrace, bcc-tools, perf and sysstat are on
the host because they probe its kernel.
Services: quadlets
A long-running container is a quadlet, a unit file systemd runs as a rootless service. The template:
pulsar setup quadlet # copy the template, enable auto-update
$EDITOR ~/.config/containers/systemd/example.container
systemctl --user daemon-reload
systemctl --user start example
myapp.containerbecomesmyapp.service.podman-auto-update.timerupdates containers labeledAutoUpdate=. The template is labeled.- Mount host paths with
:Z. Without it, SELinux denies them.
Containers that need the GPU: GPU containers.
Layering, the last resort
Layer a package onto the host only when it can’t run in a container, such as a kernel module, udev rule or host daemon:
sudo rpm-ostree install <package> # staged: takes effect at the next boot
sudo rpm-ostree uninstall <package>
A layered package applies at the next boot and stays through updates. Those then take
minutes instead of seconds. pulsar update handles it, and
pulsar doctor counts your layers. Never layer dev tools.
What not to do
-
Installing anything into
/usr/localor/opt(sudo make install, a vendor’sinstall.sh). It shadows the image onPATHand survives every update and rollback.rpm-ostree statusdoesn’t list it. Use a toolbox,miseor~/.local/bin. -
sudo pip installorsudo npm install -gon the host. It either fails, because/usris read-only, or lands in/usr/local, where no update or rollback will ever touch it. Use a venv or a toolbox. -
Editing
/etcto fix something the image ships. The edit outlives updates and hides the real fix. If you must, take a checkpoint first.
The filesystem
| Path | What it is |
|---|---|
/usr | The OS image. Read-only, even for root. Every update replaces it whole. |
/etc | Writable and local to this machine. Each deployment keeps its own copy. Updates merge your edits forward. |
/var | Writable, persistent, shared by every deployment. /home is /var/home. |
/usr/local, /opt | Links into /var. Not part of the image. No update or rollback ever cleans them. |
$HOME | Yours. Nothing on the system rolls it back. |
Written with help from AI and reviewed by a person before publishing. Spotted a mistake? Let us know.