Stop Hand-Crafting /run and /tmp Trees: Practical systemd-tmpfiles on Linux
DEV Community

Stop Hand-Crafting /run and /tmp Trees: Practical systemd-tmpfiles on Linux

Stop Hand-Crafting /run and /tmp Trees: Practical systemd-tmpfiles on Linux Most Linux hosts already run systemd-tmpfiles on every boot. Packages drop snippets under /usr/lib/tmpfiles.d/ , the early boot units create volatile trees under /run and friends, and a timer later ages junk out of /tmp and /var/tmp . Operators still keep reinventing the same work with ad-hoc shell: mkdir -p /var/lib/myapp/cache chown myapp:myapp /var/lib/myapp/cache chmod 0750 /var/lib/myapp/cache find /var/lib/myapp/cache -mtime +7 -delete That works until the next image rebuild, the next package update that races your script, or the next reboot where /run is empty again and nothing recreates the sockets directory. tmpfiles.d is the declarative replacement: one line per path, applied the same way at boot, on package install hooks, and on a periodic clean timer. What actually runs Three system units do the work (names from systemd-tmpfiles(8) ): | Unit | Role | |---|---| systemd-tmpfiles-setup-dev.service | Early device-node /dev setup | systemd-tmpfiles-setup.service | Create/adjust paths during boot (--create , with --boot lines) | systemd-tmpfiles-clean.service + .timer | Age-based cleanup (--clean ) | Typical boot invocation (documented in the same man page): systemd-tmpfiles --remove --create Useful direct calls when you change config: # Apply create/adjust rules now (safe runtime subset) sudo systemd-tmpfiles --create # Run age cleanup now sudo systemd-tmpfiles --clean # See which snippets would be merged systemd-tmpfiles --cat-config | less # Apply only one admin file sudo systemd-tmpfiles --create /etc/tmpfiles.d/myapp.conf After editing drop-ins, restarting systemd-tmpfiles-clean.service re-applies settings that are safe at runtime. Lines marked with ! only run when --boot is passed - do not force those on a live multi-user system unless you understand the impact. Config locations and precedence Search paths (system instance): - /etc/tmpfiles.d/.conf - admin overrides - /run/tmpfiles.d/.conf - runtime - /usr/lib/tmpfiles.d/.conf - vendor/package (plus/usr/local/lib/tmpfiles.d on newer releases) Same basename: /etc wins over /run wins over /usr/lib . Files are then sorted lexicographically by filename. If two different files claim the same path, the lexicographically earliest filename wins; later conflicts are logged as errors. Disable a vendor file without deleting package content: sudo ln -s /dev/null /etc/tmpfiles.d/some-vendor.conf User instance (separate from system cleanup) reads ~/.config/user-tmpfiles.d/ , $XDG_RUNTIME_DIR/user-tmpfiles.d/ , and share paths under ~/.local/share/user-tmpfiles.d/ and /usr/share/user-tmpfiles.d/ . Important nuance from the man page: system age rules for /tmp still apply to user files placed there, even if the user instance turns cleanup off. Line format #Type Path Mode User Group Age Argument d /run/myapp 0755 myapp myapp - - f /run/myapp/ready 0644 myapp myapp - - L /run/myapp/log - - - - /var/log/myapp Fields: - Type - operation letter, optional modifiers ( + ,! ,- ,= ,~ ,^ ) - Path - absolute path (specifiers like %h ,%C allowed) - Mode - e.g. 0755 ;- means default (0755 dirs,0644 files) or “don’t change” forz /Z - User / Group - name or numeric ID; use system accounts resolvable in early boot - Age - cleanup threshold ( 10d ,1h ,0 , or- ) - Argument - content, symlink target, device major:minor , ACL text, etc. Types you will actually use Create and own paths # Directory; contents eligible for age cleanup if Age is set d /run/myapp 0750 myapp myapp - - # Like d, but --remove empties it (boot wipe of leftovers) D /run/myapp/tmp 0750 myapp myapp 1d - # Create file only if missing; optional content in Argument f /var/lib/myapp/state.env 0640 myapp myapp - - # Always truncate/write content f+ /run/myapp/banner 0644 root root - Hello from tmpfiles # Write into an existing file (globs OK); w+ appends w /sys/module/dummy/parameters/foo - - - - 1 Leading parents for create types are auto-created as root:root mode 0755 . If you need different ownership on parents, add explicit d lines first. Clean without owning create # Adjust mode/owner of existing dirs + age their contents (globs OK) e /var/tmp/myapp 0750 myapp myapp 7d - e does nothing useful unless at least one of mode, user, group, or age is set. Symlinks, pipes, copy-from-factory L /etc/os-release - - - - ../usr/lib/os-release L+ /run/service/current - - - - /opt/service/releases/1.2.3 p /run/myapp/cmd.fifo 0600 myapp myapp - - # Copy from source if target missing/empty C /var/lib/myapp/defaults - - - - /usr/share/myapp/factory L /C with omitted argument pull same-named objects from /usr/share/factory/ . Cleanup exclusions and forced removal # Keep this tree during age clean of a parent x /var/tmp/myapp/keep - - - - - # Remove empty path / recursive tree (usually with ! for boot-only) r /run/myapp/stale-lock - - - - - R! /run/myapp/scratch - - - - - x excludes a path (and below) from age cleaning; it does not block r /R . Fix permissions after the fact z /var/lib/myapp/data 0750 myapp myapp - - Z /var/lib/myapp 0750 myapp myapp - - z /Z adjust mode, owner, and restore SELinux context when policy is active. They accept globs. ACLs and attributes (when you need them) a /var/lib/shared 0750 root shared - u:alice:rwx,u:bob:r-x h /var/log/immutable.log - - - - +i Type modifiers that prevent outages | Modifier | Meaning | |---|---| ! | Only with --boot (unsafe on a live system - e.g. wiping X11 locks) | - | Create failure is non-fatal (great for optional sysfs writes in containers) | + | Recreate/truncate/replace semantics for f /w /L /p /c /b (and force-copy for C+ on newer systemd) | = | If an existing object has the wrong type, remove it first | ~ on type | Base64-decode Argument before write (f /w family) | ^ on type | Read Argument from a service credential name (tmpfiles.extra / credentials docs) | Example from the manual - boot-only lock cleanup paired with a normal directory line: d /tmp/.X11-unix 1777 root root 10d r! /tmp/.X[0-9]-lock Container-safe sysctl-style write: w- /proc/sys/vm/swappiness - - - - 10 Age cleanup without foot-guns Age applies to d , D , e , v , q , Q , C , x , and X . Formats: 10d , 12h , 30m , 1w , or combinations that sum. 0 means delete matching contents every --clean run. Default “is this still young?” check uses mtime, atime, and (for non-directories) ctime. Directory ctime is ignored by default so the cleaner does not keep resurrecting directories it just touched. Narrow the clocks with age-by : # Files by mtime/birth; directories by atime; threshold 1 hour d /tmp/foo/bar - - - bmA:1h - Prefix Age with ~ to clean only one level down, not the directory’s immediate children naming semantics described in tmpfiles.d(5) - useful when the top-level entries must stay. Applications can take a shared BSD flock on a directory to pause aging for that subtree while a job runs. Lab: app runtime dir + aged cache Assume a system user myapp already exists (create with useradd --system / systemd-sysusers as you prefer). sudo tee /etc/tmpfiles.d/myapp.conf >/dev/null /dev/null . - Do not store secrets in f /w arguments; use credentials. Boundaries (what this article is not) - Not systemd-sysusers (account creation) orsystemd-firstboot (machine identity seeding) - Not RuntimeDirectory= deep-dive beyond the boundary above - Not systemd-creds secret injection (covered separately) - Not Btrfs quota design beyond noting v /q /Q exist for subvolumes - Not a replacement for backups - age cleanup deletes data on purpose References - tmpfiles.d(5) - Debian manpages - systemd-tmpfiles(8) - Debian manpages - systemd.exec(5) - RuntimeDirectory= and friends - Upstream overview: systemd project documentation for tmpfiles.d / systemd-tmpfiles Ship the snippet next to the unit, not a shell mkdir in ExecStartPre= . Your boot path stays boring - which is exactly what you want from /run and /tmp . Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.