S6-RC-COMPILE(8)
π s6-rc-compile(8) π
quick vibe check bestie: this is a System Manager's Manual page, it's s6-rc-compile(8), and it is literally the compiler for your service database. buckle up, we're going in π«Ά
πΈ NAME πΈ
s6-rc-compile β service database compiler. that's the whole job. iconic, honestly.
β¨ SYNOPSIS β¨
s6-rc-compile [-v verbosity] [-h fdhuser] [-b] compiled source...
π¦ DESCRIPTION π¦
okay so s6-rc-compile is a service database compiler. it takes a whole series of service definitions written in the source format and compiles them into a directory β that's the compiled format. and then? the administrator can go look at that compiled database through s6-rc-db(8), or put it somewhere it'll get registered at boot time as the current compiled database by s6-rc-init(8), or even live update the current service database via s6-rc-update(8). so many options, all of them slay β¨
the way it works: s6-rc-compile analyzes every directory source in its arguments. for every subdirectory service in source, it expects to find a valid service definition in service. then it outputs a compiled version of the service database into compiled. that database contains info for all the services declared in every source argument. all of them. no cap π
π Source format π
s6-rc-compile scans every source directory to find service definition directories in it. it ignores every file that isn't a directory, and it also ignores anything that starts with a dot (sorry hidden files, not today π). for every service definition directory it finds, it creates a service with the same name as the directory.
so, the naming rules, and yes there are rules:
- names cannot be duplicated.
- names cannot contain a slash or a newline.
- names can contain spaces and tabs β but honestly? using anything other than alphanumerical characters, underscores and dashes is discouraged. the s6-rc programs themselves will handle weird names just fine, no drama, but other tools β especially shell scripts β may absolutely not. so like, don't do that to yourself π
- names are also forbidden from using the reserved
s6rc-ands6-rc-prefixes. periodt.
every service definition directory service is expected to contain the following files:
π For every service
typeβ a regular file that contains only the textoneshot,longrunorbundle, plus a terminating newline. this file declares the type of service defined by the directory. it's giving "the whole personality in one file" πflag-essentialβ an optional regular file. the contents are completely irrelevant, only its presence is tested. if this file exists, the service gets marked as essential, which means ans6-rc -d change foocommand will not stop the service β only ans6-rc -D change foocommand will. and if the service is a bundle, the flag gets propagated to all its contents, i.e. all the services it represents get marked as essential too. it spreads bestie, it spreads πflag-recommendedβ an optional regular file. again, contents irrelevant, only presence is tested. if it exists, the service gets marked as recommended. heads up though: this is only important fors6-rc-repo(7)commands, not when compiling the database. so it's lowkey just vibes over here π¦
π§ For bundles
contents.dβ a directory. it must contain files, no matter their type, no matter their contents, named after services. (typically empty regular files are used, that's the move.) all the services named in this directory will be represented by the bundle named service.contentsβ ifcontents.ddoes not exist, the bundle contents can be read from a regular file namedcontentsinstead. this file must be a list of service names, one per line. whitespace at the beginning of a line is ignored, but trailing whitespace is not (rude, i know). lines starting with a#character are ignored. the file defines the services that will be represented by the bundle named service. β οΈ note that this format is deprecated.
so yes, you can use bundle names inside acontents.ddirectory or acontentsfile. but ifs6-rc-compiledetects a cycle in bundle definitions, it will complain and exit1. a whole loop of bundles eating each other? not the vibe. π
πΈ For atomic services
timeout-upβ an optional regular file. if it exists, it must contain an integer, which is the max number of millisecondss6-rc(8)will wait for successful completion of the service start. if starting the service takes longer than this value,s6-rc(8)will declare the transition a failure. if the file doesn't exist, or contains0, no timeout is defined ands6-rc(8)will wait indefinitely for the service to start. forever. patient king πtimeout-downβ an optional regular file. if it exists, it must contain an integer, which is the max number of millisecondss6-rc(8)will wait for successful completion of the service stop. if stopping the service takes longer than this value,s6-rc(8)will declare the transition a failure. if the file doesn't exist, or contains0, no timeout is defined ands6-rc(8)will wait indefinitely for the service to stop. same energy, other direction π¦dependencies.dβ an optional directory. it must contain files, no matter their type, no matter their contents, named after services. (typically empty regular files are used.) all the services named in this directory will be direct dependencies of service, i.e. the services that must be up in order for service to work properly. your ride-or-dies, basically π«Άdependenciesβ ifdependencies.ddoes not exist: an optional regular file. it must be a list of service names, one per line. whitespace at the beginning of a line is ignored, but trailing whitespace is not. lines starting with a#character are ignored. the file defines the direct dependencies of service. β οΈ note that this format is deprecated.
okay and this part is important so listen up: it is unnecessary to manually define complete sets of dependencies in the dependencies.d directory or the dependencies file, because s6-rc(8) will properly handle dependency chains. like β if A depends on B, no matter the underlying implementation of B, and the current implementation of B depends on C, then you should just put B in A/dependencies.d. when starting the set, s6-rc(8) will start C first, then B, then A. and if the underlying implementation of B changes and stops depending on C? then you just have to modify the dependencies for B, and the definition of A is still correct. that's the whole flex. that's why this design ate π
of course β if A depends on C anyway, you should add both B and C to A/dependencies.d. don't leave C hanging.
and ifs6-rc-compiledetects a cycle in dependencies across services, it will complain and exit1. again: cycles are not the vibe. π
β For oneshots
upanddownβ two regular files, which must each contain a single Unix command line. the files get interpreted by theexeclineb(1)lexer at compile time, and the results are stored into the compiled database in an internal form.upgets run when the service is started, anddowngets executed when the service is stopped.upis mandatory, butdownis optional β if nodownfile is provided in the source definition directory, then it's treated as the empty script. and if a script is empty,s6-rc(8)will consider that the corresponding transition for this service does nothing and always succeeds. free win π
so up and down are interpreted by execlineb(1), but that does not mean they have to be entirely written in the execline language. the execlineb(1) lexer is only used because it can compile a Unix command line from a text file and store the compiled result, whereas a shell would have to be invoked every time the script is run. there are many ways to write up and down scripts:
- they can be written as standard execline scripts, without the initial shebang line or positional parameter management.
- they can be written in any scripting language by invoking the interpreter directly: for instance
/bin/sh -c βscriptβ, where script is a shell script. - they can also just call a script that will actually be stored somewhere else: for instance,
upcan contain/etc/init.d/service startwhiledowncontains/etc/init.d/service stop, and/etc/init.d/serviceis a System V-style init script.
and please, bestie, don't think you have to learn all the intricacies of the execline language just because the up and down scripts get lexed by it. you don't. breathe. you're fine ππ«Ά
π For longruns
the s6-rc service definition directory for a longrun service is similar to an s6-service-directory(7), but there are a few differences:
s6-rc-compilecrafts the servicedir itself, based on what it finds in the service definition directory. it does not copy everything directly from the definition directory to the servicedir β only two subdirectories get copied verbatim:dataandenv. so if you want to store service configuration data, to be used by the run script, in the service directory, make sure it's in adata/orenv/subdirectory. put it anywhere else and it's just gone, sorry π- definition directories cannot have a
logsubdirectory β or if they do, it will be ignored. froms6-rc-compile's point of view, logged s6 services must actually be defined as two separate s6-rc services, one defined as a producer and one defined as a consumer, making a pipeline of just two services; see below for more information about pipelines. two services, one pipeline, main character energy π
the following files must or may appear in a longrun definition directory:
- a mandatory regular file named
run, as well as optional files namedfinish,notification-fd,lock-fd,timeout-kill,timeout-finish,max-death-tally, anddown-signal. these files will be copied, or recreated, in the generateds6-service-directory(7): they're meant to be used by thes6-supervise(8)process that will manage the longrun service. that's the whole squad right there π«Ά - optional directories named
instanceandinstances. these will be copied verbatim into the generated service directory. they're used internally by some s6 tools; using them for your own purposes is not recommended β you should ignore them and use thedatadirectory instead. like, respectfully, they're not for you π - optional directories named
dataandenv. these will be copied verbatim into the generated service directory. yes, these ones. use these π - an optional file named
producer-for. if this file exists, then it must contain the name of another longrun service servicelog; service is then declared as a producer for servicelog. servicelog must also, in its own definition directory, be declared as a consumer for at least service. it takes two to pipeline, bestie β¨ - an optional file named
consumer-for. if this file exists, then it must contain a list of longrun services serviceprod...: service is then declared as a consumer for all the services in serviceprod. each of those services must also, in its own definition directory, be declared as a producer for service. mutual, consensual, compile-time-verified π pipeline-nameis an optional file π β if it's sitting there and there's aconsumer-forfile next to it, and there's noproducer-forfile, then a bundle gets created automatically, named after whatever's inside thepipeline-namefile, and it contains every service in the pipeline that ends at service. Peep the pipelining section below for the whole story. And heads up: thepipeline-namefile is ignored if service isn't a last consumer. Periodt.
Lowkey important: service directories get relocated and copied at boot time π¦ β so if your run or finish scripts point at files inside the service directory (probably in the data or env subdirectories!), they better use relative paths, not absolute ones. Fr fr.
Also you literally cannot create a./downfile for s6-supervise(8) in a generated service directory π. Even if such a file exists in the definition directory, it gets ignored β it will not be replicated into the service directory. This is intentional: s6-rc(8) internally uses/downfiles in the service directories it manages, to mark longrun services that are down. So don't fight it, bestie π .
The producer-for, consumer-for and pipeline-name files are what set up automatic longrun pipelining β¨.
π Longrun pipelining π
If you've used supervision suites before, you already know logged services: one service acts as a producer, gets coupled with another service β its logger β and the supervision system automatically keeps an open pipe between the producer's stdout and the logger's stdin. Iconic behaviour, honestly.
s6-rc(8) comes with an extension of this whole mechanism π₯. Instead of only letting two longrun services be pipelined, it can set up an indefinite number of longrun services this way. Ate.
- A producer declares its direct consumer in a
producer-forfile. - Intermediate services declare both their direct producers in their
consumer-forfile, and their direct consumer in theirproducer-forfile. - The last consumer only declares its direct producers in a
consumer-forfile. - The last consumer may also declare a name for the whole pipeline, in its
pipeline-namefile. If it does, then a bundle is automatically created with that given name, and it contains all the services in the pipeline. π
s6-rc-compile will detect pipelines and set up the service directories so that every producer's stdout is connected to its consumer's stdin β and so that the pipes don't break whenever one element in the chain dies π.
A service can only be declared as a producer for one other service, but it can be declared as a consumer for several other services: the consumer-for can have several lines. Translation: one service can only send its output to one consumer, but it can read its input from several producers. And if there are several producers feeding a service, they all write to the same Unix pipe that the service reads from π«Ά.
Which meansβ¦ what s6-rc calls "pipelines" are really funnels π. You can collapse multiple data streams into a single data stream at every step of your processing chain. The pipeline terminology just stuck around for history reasons: previous versions of s6-rc(8) couldn't handle multiple producers. Iykyk.
s6-rc-compile checks for pipeline consistency π. It must see a producer-for file in the producers' definition that's consistent with the consumer-for file in the consumer's definition. It will detect and reject cycles as well as collisions. No shortcuts, no chaos.
The pipe linking a set of producers with a consumer is created and stored at run-time in an s6-fdholder-daemon(8) instance managed by an automatically generated longrun service named s6rc-fdholder π¦.
π§ Compiled database usage π§
Once it's been built with s6-rc-compile, a compiled database cannot be modified. It β meaning the directory containing the n, db and resolve.cdb files, plus the servicedirs subdirectory β can be moved around in the filesystem, as long as the insides stay untouched π.
Once a compiled database is live (i.e. it has services running on it, following an invocation of s6-rc-init(8) or s6-rc-update(8)), it must not move anymore, and it must not be deleted β οΈ. The only way to "free" such a compiled database for displacement or deletion is to replace it as the live one via another call to s6-rc-update(8). That's the whole escape hatch, bestie.
And no matter what user compiles the database, only root and the user owning the supervision tree at run-time will be able to operate the compiled database with the s6-rc(8) command π
β¨ OPTIONS β¨
-vverbosity β be more or less verbose. Default is1: warning and error messages get printed to stderr.0silences warnings.2adds a bit more info about whats6-rc-compileis doing.3or more is heavy debug output π.-hfdhuser β arrange for the s6-fdholder-daemon(8) program (the one that maintains the pipes for the longrun pipelines) to run as user fdhuser. By default it runs as the user owning the supervision tree, i.e. most likelyroot. π-bβ make s6-rc-oneshot-run(8) invocations wait instead of fail on lock contention. This shouldn't change anything in practice, and you can literally just ignore that option π.
π EXIT STATUS π
0β Success. Slay. πΈ1β Error in a source directory.100β Wrong usage.111β System call failed.
π EXAMPLES π
The examples/source subdirectory of the s6-rc package contains a set of service definition directories, and it's actually a working, valid set for a Linux system running busybox and the skarnet.org packages π«Ά. Of course, only the service definition set has been kept and private information has been removed, so it won't work out-of-the-box without the proper specific files β notably configuration in /etc β but nevertheless, you can browse the source and understand what it does, and adapt it to your own needs. It will compile as-is with s6-rc-compile, and you can examine the resulting compiled database with s6-rc-db(8). π
π SEE ALSO π
- s6-rc-faq(7)
- s6-rc-overview(7)
- s6-rc-why(7)
- s6-rc(8)
- s6-rc-bundle(8)
- s6-rc-db(8)
- s6-rc-dryrun(8)
- s6-rc-fdholder-filler(8)
- s6-rc-format-upgrade(8)
- s6-rc-init(8)
- s6-rc-oneshot-run(8)
- s6-rc-update(8)
This man page is ported from the authoritative documentation at: https://skarnet.org/software/s6-rc/s6-rc-compile.html
π AUTHORS π
- Laurent Bercot
- Alexis
<flexibeast@gmail.com>(man page port)
Okay that's the whole page, bestie, you made it to the end ππ β this one's from the Debian packaging of s6-rc-compile(8), dated October 21, 2025. Go build yourself a database, you absolute icon. β¨π¦
π¦ where this page comes from
- nixpkgs unstable: s6-rc 0.7.0.0
- nixpkgs unstable: s6-rc-man-pages 0.6.0.0.1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms