FHS Package Placement

The Filesystem Hierarchy Standard (FHS) is the Linux Foundation’s specification for where files belong on a Unix-like system; version 3.0 (2015-03-19) is the current release, and the hier(7) man page condenses the same layout as the directories of “a typical Linux system.”12 This page covers the parts of the standard that answer the recurring packaging question: given a pile of files that aren’t a compiled program, where do they go?

The organizing axes

FHS sorts files along two axes: shareable vs. host-specific and static vs. variable. /usr is defined as shareable, read-only data — mountable by many FHS-compliant hosts and never written to at runtime — so anything host-specific or time-varying is pushed elsewhere: configuration to /etc, variable state to /var. The root filesystem carries only what is needed to boot, restore, recover, and repair the system; /usr, /opt, and /var are explicitly designed to live on other partitions.3

share vs. lib vs. libexec vs. opt

DirectoryHoldsPlacement rule
/usr/shareArchitecture-independent data (docs, man pages, text and data files)Default home for package data that isn’t compiled
/usr/libObject files, libraries, and internal binariesOne subdirectory per application, for architecture-dependent or executable material
/usr/libexecInternal binaries run by other programs, never directly by usersAdded in FHS 3.0; an application using libexec must not also stash internal binaries in /usr/lib
/optSelf-contained add-on application bundlesMonolithic vendor packages carrying their own internal bin/lib/etc

/usr/share is for “all read-only architecture independent data files”; a package whose data needs no modification should store it there, preferably in its own subdirectory (/usr/share/misc absorbs single files).4 A /usr/lib subdirectory carries the mirror-image constraint: architecture-dependent data used exclusively by the application must stay inside it. /opt is the bundle path — user-invoked programs go in /opt/<package>/bin, configuration belongs in /etc/opt, variable data in /var/opt; hier(7) compresses this to “add-on packages that contain static files.”5 /usr/local replicates the /usr layout (bin, lib, share, …) for software installed outside the distribution’s package manager.6

The plugin-ecosystem sub-convention

Many packages exist to be enumerated and merged into a shared directory by an outside tool — shell completions, editor plugins, shell startup files — rather than to own private data. These ecosystems converge on ecosystem-first naming under share: share/vim-plugins/<pkg>, share/tmux-plugins/<pkg>, share/bash-completion/completions/, share/zsh/site-functions/, share/fish/vendor_conf.d/. nixpkgs encodes the pattern directly: the tmux plugin builder sets rtpPath = "share/tmux-plugins" and its install phase copies each plugin derivation into $out/share/tmux-plugins/<name>.7 Package-first share/<pkg>/ remains right for a package’s own private data (its man pages, docs, and assets); the ecosystem-first form exists because the consuming tool wants one directory to scan, and because plugin packages would collide under generic names in a package-first layout.

Debian’s elaboration

Debian Policy chapter 9 mandates FHS 3.0 compliance with enumerated exceptions. The notable relaxation: FHS’s rule that architecture-independent application files live in /usr/share is downgraded to a suggestion, so a /usr/lib subdirectory may hold a mixture of architecture-independent and architecture-dependent files — but a directory composed entirely of architecture-independent files should still be in /usr/share.8 This is why Debian systems carry many mixed /usr/lib/<pkg> directories that a strict reading of the FHS would place under /usr/share.

Application: skills packages in a Nix profile

A Nix profile emulates /usr, not / — its bin/, lib/, and share/ are merged symlink forests into the store, and the Nix store itself already plays the /opt role (self-contained bundles, each with internal structure), so opt/ inside a profile is essentially unheard of. For agent skill packages (markdown, templates, maybe scripts — architecture-independent data consumed by an enumerating tool), the defensible placement is ecosystem-first under share: share/opencode-skills/<skill>/, share/hermes-skills/<skill>/, or a single share/agent-skills/<skill>/ tree feeding several tools. lib/<pkg>/skills and lib/skills/<pkg> are a category error — that layout declares compiled, private code.910

The profile hop itself is optional. Because profile entries are pure symlinks, home-manager can link a skill package’s store path directly to its final location (home.file.".hermes/skills/<skill>", xdg.configFile."opencode/skills/<skill>") with no profile detour; installing through home.packages only buys the ability to browse the tree under ~/.nix-profile/share.

Cross-domain connections

Sources

Footnotes

  1. LSB Workgroup, The Linux Foundation 2015 — Filesystem Hierarchy Standard, Version 3.0 ↩

  2. 2026 — hier(7) — Linux manual page (man-pages 6.19) ↩

  3. LSB Workgroup, The Linux Foundation 2015 — Filesystem Hierarchy Standard, Version 3.0 ↩

  4. LSB Workgroup, The Linux Foundation 2015 — Filesystem Hierarchy Standard, Version 3.0 ↩

  5. 2026 — hier(7) — Linux manual page (man-pages 6.19) ↩

  6. LSB Workgroup, The Linux Foundation 2015 — Filesystem Hierarchy Standard, Version 3.0 ↩

  7. nixpkgs: pkgs/misc/tmux-plugins/default.nix (nixos-unstable) ↩

  8. Debian Policy Manual — Chapter 9: The Operating System ↩

  9. LSB Workgroup, The Linux Foundation 2015 — Filesystem Hierarchy Standard, Version 3.0 ↩

  10. nixpkgs: pkgs/misc/tmux-plugins/default.nix (nixos-unstable) ↩