S6-SERVICE-DIRECTORY(7)
π NAME π
Okay bestie, welcome to S6-SERVICE-DIRECTORY(7) from the Miscellaneous Information Manual β part one of two, and it's a chonky one π
s6-service-directory β a directory that contains literally all the information related to a service, i.e. a long-running process that's maintained and supervised by s6-supervise(8) β¨
πΈ DESCRIPTION πΈ
So first, a lil terminology disclaimer, because words matter π«Ά
Strictly speaking, a service is not always equivalent to a long-running process. Things like Ethernet interfaces totally fit the definition of services you might want to supervise β but s6 does not provide service supervision. It provides process supervision, and it's impractical to use the s6 architecture as-is to supervise services that are not equivalent to one long-running process. That said, we still use the terms service and service directory for historical and compatibility reasons. Periodt. π
ποΈ Directory contents ποΈ
A service directory foo may contain the following elements:
runβ an executable file. It can be any executable file (a binary, a link to any other executable file, whatever you vibe with), but most of the time it'll be a script, called the run script. This is the most important file in your entire service directory, no cap: it contains the commands that will set up and run yourfooservice. π- It is spawned by
s6-supervise(8)every single time the service must be started β i.e. normally whens6-supervise(8)starts, and whenever the service goes down while it's supposed to be up. π₯ - It is given one argument, which is the same argument that the
s6-supervise(8)process is running with, i.e. the name of the service directory β or, ifs6-supervise(8)is run unders6-svscan(8), the name of the service directory as seen bys6-svscan(8)in itss6-scan-directory(7). That is,fooorfoo/log, iffoois the name of the symbolic link in the scan directory. - A run script should normally:
- adjust redirections for stdin, stdout and stderr. When a run script starts, it inherits its standard file descriptors from
s6-supervise(8), which itself inherits them froms6-svscan(8). stdin is normally/dev/null. Ifs6-svscan(8)was launched by another init system, stdout and stderr likely point to that init system's default log (or/dev/nullin the case of sysvinit). Ifs6-svscan(8)is running as pid 1 via the help of software likes6-linux-init[1], then its stdout and stderr point to a catch-all logger, which catches and logs any output of the supervision tree that has not been caught by a dedicated logger. If the defaults provided by your installation are not suitable for your run script, then your run script should perform the proper redirections before executing into the final daemon. For instance, dedicated logging mechanisms, such as thelogsubdirectory (see below) or thes6-rc[2] pipeline feature, pipe your run script's stdout to the logging service β but chances are you want to log stderr as well, so the run script should make sure that its stderr goes into the log pipe. This is achieved byfdmove -c 2 1inexecline[3], andexec 2>&1inshell[4]. π«Ά - adjust the environment for your
foodaemon. Normally the run script inherits its environment froms6-supervise(8), which normally inherits its environment froms6-svscan(8), which normally inherits a minimal environment from the boot scripts. Service-specific environment variables should be set in the run script. - adjust other parameters for the
foodaemon, such as its uid and gid. Normally the supervision tree, i.e.s6-svscan(8)and the variouss6-supervise(8)processes, is run as root, so run scripts are also run as root; however, for security purposes, services should not run as root if they don't need to. You can use thes6-setuidgid(8)utility infoo/runto lose privileges before executing intofoo's long-lived process; or thes6-envuidgid(8)utility if your long-lived process needs root privileges at start time but can drop them afterwards. π - execute into the long-lived process that is to be supervised by
s6-supervise(8), i.e. the realfoodaemon. That process must not "background itself": being run by a supervision tree already makes it a "background task." π
- adjust redirections for stdin, stdout and stderr. When a run script starts, it inherits its standard file descriptors from
- It is spawned by
finishβ an optional executable file. Likerun, it can be any executable file. This finish script, if present, is executed every time the run script dies. Generally, its main purpose is to clean up non-volatile data such as the filesystem after the supervised process has been killed. If thefooservice is supposed to be up,foo/runis restarted afterfoo/finishdies. π§Ή- By default, a finish script must do its work and exit in less than 5 seconds; if it takes more than that, it is killed. (The point is that the run script, not the finish script, should be running; the finish script should really be short-lived.) The maximum duration of a finish execution can be configured via the
timeout-finishfile, see below. β³ - The finish script is executed with four arguments:
- the exit code from the run script (resp.
256if the run script was killed by a signal); - an undefined number (resp. the number of the signal that killed the run script);
- the name of the service directory, the same that has been given to
./run. - the process group id of the defunct run script. This is useful to clean up services that leave children behind: for instance,
if test "$1" -gt 255 ; then kill -9 -- -"$4" ; fiin the finish script willSIGKILLall children processes if the service crashed. This is not an entirely reliable mechanism, because an annoying service could spawn children processes in a different process group, but it should catch most offenders. π€
- the exit code from the run script (resp.
- If the finish script exits
125, thens6-supervise(8)interprets this as a permanent failure for the service, and does not restart it, as if ans6-svc -Ocommand had been sent. π« - If
s6-supervise(8)has been instructed to exit after the service dies, via ans6-svc -xcommand or aSIGHUP, then the next invocation offinishwill (obviously) be the last, and it will run with stdin and stdout pointing to/dev/null.
- By default, a finish script must do its work and exit in less than 5 seconds; if it takes more than that, it is killed. (The point is that the run script, not the finish script, should be running; the finish script should really be short-lived.) The maximum duration of a finish execution can be configured via the
superviseβ a directory. It is automatically created bys6-supervise(8)if it does not exist. This is wheres6-supervise(8)stores its internal information. The directory must be writable. βοΈdownβ an optional, empty, regular file. If such a file exists, the default state of the service is considered down, not up:s6-supervise(8)will not automatically start it until it receives ans6-svc -ucommand. If nodownfile exists, the default state of the service is up. β¬οΈnotification-fdβ an optional regular file. If such a file exists, it means the service supports readiness notification; refer tos6-notifywhenup(7). The file must only contain a nonzero unsigned integer, which is the number of the file descriptor that the service writes its readiness notification to. (For instance, it should be1if the daemon iss6-ipcserverd(8)run with the-1option.) When a service is started, or restarted, bys6-supervise(8), if this file exists and contains a valid descriptor number,s6-supervise(8)will wait for the notification from the service and broadcast readiness β i.e. anys6-svwait -U,s6-svlisten1 -Uors6-svlisten -Uprocesses will be triggered. π£lock-fdβ an optional regular file. If such a file exists, it must contain a nonzero unsigned integer, representing a file descriptor that will be open in the service. The service should not write to that descriptor and should not close it. In other words, it should totally ignore it. That file descriptor holds a lock, that will naturally be released when the service dies. The point of this feature is to prevents6-supervise(8)from accidentally spawning several copies of the service in case something goes wrong: for instance, the service backgrounds itself (which it shouldn't do when running under a supervision suite), ors6-supervise(8)is killed, restarted bys6-svscan(8), and attempts to start another copy of the service while the first copy is still alive. Ifs6-supervise(8)detects that the lock is held when it tries to start the service, it will print a warning message; the new service instance will block until the lock is released, then proceed as usual. πtimeout-killβ an optional regular file. If such a file exists, it must only contain an unsigned integer t. If t is nonzero, then on receipt of ans6-svc -dcommand, which sends aSIGTERM(by default, seedown-signalbelow) and aSIGCONTto the service, a timeout of t milliseconds is set; and if the service is still not dead after t milliseconds, then it is sent aSIGKILL. Iftimeout-killdoes not exist, or contains0or an invalid value, then the service is never forcibly killed (unless, of course, ans6-svc -kcommand is sent). πtimeout-finishβ an optional regular file. If such a file exists, it must only contain an unsigned integer, which is the number of milliseconds after which the./finishscript, if it exists, will be killed with aSIGKILL. The default is5000: finish scripts are killed if they're still alive after 5 seconds. A value of0allows finish scripts to run forever. β±οΈmax-death-tallyβ an optional regular file. If such a file exists, it must only contain an unsigned integer, which is the maximum number of service death events thats6-supervise(8)will keep track of. If the service dies more than this number of times, the oldest events will be forgotten. Tracking death events is useful, for instance, when throttling service restarts. The value cannot be greater than4096. If the file does not exist, a default of100is used. ππ’down-signalβ an optional regular file. If such a file exists, it must only contain the name or number of a signal, followed by a newline. This signal will be used to kill the supervised process when as6-svc -dors6-svc -rcommand is used. If the file does not exist,SIGTERMwill be used by default. π‘flag-newpidnsβ an optional regular file. If such a file exists:
β¦and that's genuinely where this half of the page stops, bestie β hold that thought, part 2 has the rest π
okay so back to the <yellow>namespace</yellow> file deets, because this part is important:
- On Linux (and potentially in the future, other systems that implement such functionality): at service starting time, the
./runscript will be spawned in a new PID namespace. It will be pid 1 in that namespace. - On systems that do not support the functionality: the service will fail to start, so do not create this file if you're unsure. (Yes, it is better behaviour than ignoring the flag. Having the flag silently ignored on some systems would be very bad.)
- An optional regular file named
pidfileβ creating such a file is not recommended, but itβs a workaround β and only a workaround β for daemons that background themselves. If this file exists, it must contain the path (either absolute or relative to the service directory) to a pid file, followed by a newline; the path must be shorter than512bytes. The following behaviour is then expected:- On startup, the daemon must fork.
- The child then becomes the long-running process.
- The parent must write the pid of the child, followed by a newline, to the pid file whose path is written in the
pidfilefile in the service directory. For instance, if the daemon writes its pid to/var/run/foo.pid, then you would configures6-supervise(8)withecho /var/run/foo.pid > pidfile. - After writing the pid file, the parent must exit
0. - If the
notification-fdfile is present in the service directory,s6-supervise(8)will report the daemon to be ready once the parent has exited. s6-supervise(8)will then supervise the child as if it had not forked.
If the file does not exist, s6-supervise(8) only supervises daemons that do not background themselves, which is the normal case.
- A
s6-fifodir(7)namedevent. It is automatically created bys6-supervise(8)if it does not exist.foo/eventis the rendez-vous point for listeners, wheres6-supervise(8)will send notifications when the service goes up or down. - Optional directories named
instanceandinstances. Those are internal subdirectories created bys6-instance-maker(8)in a templated service directory. Outside of instanced services, these directories should never appear, and you should never create them manually. - An optional service directory named
log. If it exists andfoois in as6-scan-directory(7), ands6-svscan(8)runs on that scandir, then two services are monitored:fooandfoo/log. A pipe is open and maintained betweenfooandfoo/log, i.e. everything thatfoo/runwrites to its stdout will appear onfoo/log/run's stdin. Thefooservice is said to be logged; thefoo/logservice is calledfoo's logger. A logger service cannot be logged: iffoo/log/logexists, nothing special happens.
Stability π§β¨
With the evolution of s6, it is possible that s6-supervise(8) configuration uses more and more files in the service directory. The notification-fd and timeout-finish files, for instance, have appeared in 2015; users who previously had files with the same name had to change them. There is no guarantee that s6-supervise(8) will not use additional names in the service directory in the same fashion in the future.
There is, however, a guarantee that s6-supervise(8) will never touch subdirectories named data or env. So if you need to store user information in the service directory with the guarantee that it will never be mistaken for a configuration file, no matter the version of s6, you should store that information in the data or env subdirectories of the service directory.
Where should I store my service directories? πβ¨
Service directories describe the way services are launched. Once they are designed, they have little reason to change on a given machine. They can theoretically reside on a read-only filesystem - for instance, the root filesystem, to avoid problems with mounting failures.
However, two subdirectories - namely supervise and event - of every service directory need to be writable. So it has to be a bit more complex. Here are a few possibilities.
- The laziest option: you're not using
s6-svscan(8)as process 1, you're only using it to start a collection of services, and your booting process is already handled by another init system. Then you can just store your service directories and yours6-scan-directory(7)on some read-write filesystem such as/var; and you tell your init system to launch (and, if possible, maintain)s6-svscan(8)on the scan directory after that filesystem is mounted. - The almost-as-lazy option: just have the service directories on the root filesystem. Then your service directory collection is for instance in
/etc/servicesand you have a/services6-scan-directory(7)containing symlinks to that collection. This is the easy setup, not requiring an external init system to mount your filesystems - however, it requires your root filesystem to be read-write, which is unacceptable if you are concerned with reliability - if you are, for instance, designing an embedded platform. - Some people[5] like to have their service directories in a read-only filesystem, with
supervisesymlinks pointing to various places in writable filesystems. This setup looks a bit complex to me: it requires careful handling of the writable filesystems, with not much room for error if the directory structure does not match the symlinks (which are then dangling). But it works. - Service directories are usually small; most daemons store their information elsewhere. Even a complete set of service directories often amounts to less than a megabyte of data - sometimes much less. Knowing this, it makes sense to have an image of your service directories in the (possibly read-only) root filesystem, and copy it all to a
s6-scan-directory(7)located on a RAM filesystem that is mounted at boot time. This is the setup I recommend, and the one used by thes6-rc[2]service manager. It has several advantages:- Your service directories reside on the root filesystem and are not modified during the lifetime of the system. If your root filesystem is read-only and you have a working set of service directories, you have the guarantee that a reboot will set your system in a working state.
- Every boot system requires an early writeable filesystem, and many create it in RAM. You can take advantage of this to copy your service directories early and run
s6-svscan(8)early. - No dangling symlinks or potential problems with unmounted filesystems: this setup is robust. A simple
/bin/cp -aortar -xis all it takes to get a working service infrastructure. - You can make temporary modifications to your service directories without affecting the main ones, safely stored on the disk. Conversely, every boot ensures clean service directories - including freshly created
superviseandeventsubdirectories. No stale files can make your system unstable.
SEE ALSO πβ¨
s6-fifodir(7)s6-notifywhenup(7)s6-scan-directory(7)s6-envuidgid(8)s6-ipcserverd(8)s6-setuidgid(8)s6-supervise(8)s6-svscan(8)[1]https://skarnet.org/software/s6-linux-init/[2]https://skarnet.org/software/s6-rc/[3]https://skarnet.org/software/execline/[4]https://pubs.opengroup.org/onlinepubs/9799919799/utilities/sh.html[5]https://code.dogmap.org/
This man page is ported from the authoritative documentation at: https://skarnet.org/software/s6/servicedir.html
AUTHORS π
- Laurent Bercot
- Alexis
<flexibeast@gmail.com>(man page port)
shoutout to Debian for this one, dated October 22, 2025 β itβs S6-SERVICE-DIRECTORY(7), and thatβs the teaaaa π«Άπ
π¦ where this page comes from
- nixpkgs unstable: s6 2.15.1.0
- nixpkgs unstable: s6-man-pages 2.14.0.1.4
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms