SYSTEMD-RUN(1)
๐ systemd-run โ the systemd-run manual, but make it make sense โจ
okay bestie, so systemd-run lives in the systemd family (that's the manual name, it's giving SYSTEMD-RUN(1) energy ๐
) and it's literally the "run my program but make it a unit" command. Let's go ๐ฆ
๐ SYNOPSIS ๐
systemd-run [OPTIONS...] COMMAND [ARGS...]
systemd-run [OPTIONS...] [PATH OPTIONS...] {COMMAND [ARGS...]}
systemd-run [OPTIONS...] [SOCKET OPTIONS...] {COMMAND [ARGS...]}
systemd-run [OPTIONS...] [TIMER OPTIONS...] {COMMAND [ARGS...]}
โจ DESCRIPTION โจ
So like, systemd-run is how you create and start a transient .service or .scope unit and run the COMMAND you gave it inside that unit. It can also create and start a transient .path, .socket, or .timer unit โ and that unit activates a .service unit when it elapses. Iconic multitasking behavior honestly ๐
If you run a command as a transient service unit, it gets started and managed by the service manager like any other service, which means it shows up in systemctl list-units like any other unit. Bestie it runs in a clean and detached execution environment, with the service manager as its parent process. In this mode, systemd-run starts the service asynchronously in the background and returns right after the command has begun execution โ unless --no-block, --wait, --pipe, or --pty are specified (more on those below, iykyk).
If you run a command as a transient scope unit, it gets executed by systemd-run itself as parent process, so it inherits the execution environment of the caller. But the processes of the command are still managed by the service manager, similar to normal services, and they do show up in the output of systemctl list-units. In this case execution is synchronous, and it returns only when the command finishes. You turn this mode on with the --scope switch (see below ๐ซถ).
If you run a command with path, socket, or timer options like --on-calendar= (see below), a transient path, socket, or timer unit is created alongside the service unit for the specified command. Only the transient path, socket, or timer unit is started immediately โ the transient service unit gets triggered by that path, socket, or timer unit. And if the --unit= option is specified, the COMMAND may be omitted. In that case, systemd-run creates only a .path, .socket, or .timer unit that triggers the specified unit. No command, no problem ๐โ๏ธ
By default, services created with systemd-run default to the simple type โ check the description of Type= in systemd.service(5) for the details. But โ ๏ธ note this: when this type is used, the service manager (and therefore the systemd-run command) considers service start-up successful as soon as the fork() for the main service process succeeded, i.e. before the execve() is invoked โ which means it counts as success even if the specified command can't actually be started. So if you want systemd-run to return successfully only when the specified command line has genuinely been started, consider using the exec service type, i.e. --property=Type=exec. Do that. Trust ๐ธ
After systemd-run passes the command to the service manager, the manager performs variable expansion. Which means: dollar characters ("$") that should not be expanded need to be escaped as "$$". You can also just turn expansion off entirely with --expand-environment=no. Periodt ๐
๐ OPTIONS ๐
The following options are understood (there are a LOT, hydrate first bestie):
--scopeโ Create a transient.scopeunit instead of the default transient.serviceunit (see above ๐).
Added in version 206.
--unit=,-uโ Use this unit name instead of an automatically generated one. Name it yourself, main character energy.
Added in version 206.
--property=,-pโ Sets a property on the scope or service unit that is created. This option takes an assignment in the same format assystemctl(1)'sset-propertycommand.
Added in version 211.
--description=โ Provide a description for the service, scope, path, socket, or timer unit. If you don't specify one, the command itself gets used as the description. SeeDescription=insystemd.unit(5).
Added in version 206.
--slice=โ Make the new.serviceor.scopeunit part of the specified slice, instead ofsystem.slice(when running in--systemmode) or the root slice (when running in--usermode).
Added in version 206.
--slice-inheritโ Make the new.serviceor.scopeunit part of the slice thatsystemd-runitself has been invoked in. You can combine this with--slice=, and in that case the slice specified via--slice=is placed within the slice thesystemd-runcommand was invoked in.
Example bestie: say systemd-run is invoked in the slice foo.slice, and the --slice= argument is bar. The unit will then be placed under foo-bar.slice. Slay.
Added in version 246.
--expand-environment=BOOLโ Expand environment variables in command arguments. If enabled (that's the default), environment variables specified as"${VARIABLE}"get expanded the same way as in commands specified viaExecStart=in units. With--scope, this expansion is performed bysystemd-runitself, and in other cases by the service manager that spawns the command. Note that this is similar to โ but not the same as โ variable expansion inbash(1)and other shells.
See systemd.service(5) for a description of variable expansion. Disabling variable expansion is useful if the specified command includes, or may include, a "$" sign.
Added in version 254.
-r,--remain-after-exitโ After the service process has terminated, keep the service around until it is explicitly stopped. Super useful for collecting runtime information about the service after it finished running. Also seeRemainAfterExit=insystemd.service(5). ๐ซ This option may not be combined with--wait,--scope,--pty,--pty-late, or--pipe.
Added in version 207.
--send-sighupโ When terminating the scope or service unit, send aSIGHUPimmediately afterSIGTERM. This is useful to indicate to shells and shell-like processes that the connection has been severed. Also seeSendSIGHUP=insystemd.kill(5).
Added in version 207.
--service-type=โ Sets the service type. Also seeType=insystemd.service(5). This option has no effect in conjunction with--scope. Defaults tosimple.
Added in version 211.
--uid=,--gid=โ Runs the service process under the specified UNIX user and group. Also seeUser=andGroup=insystemd.exec(5).
Added in version 211.
--nice=โ Runs the service process with the specified nice level. Also seeNice=insystemd.exec(5).
Added in version 211.
--working-directory=โ Runs the service process with the specified working directory. Also seeWorkingDirectory=insystemd.exec(5).
Added in version 240.
--same-dir,-dโ Similar to--working-directory=, but uses the current working directory of the caller for the service to execute. "wherever I am, you are" vibes ๐ฅบ
Added in version 240.
--root-directory=โ Runs the service process with the specified root directory. Also seeRootDirectory=insystemd.exec(5).
โ ๏ธ Note that the path is looked up inside the file system namespace that systemd-run is running in, which might be different than the file system namespace the manager process is running in. Use the RootDirectory= property directly if you want the path to be looked up in the manager process's file system namespace.
Added in version 259.
--same-root-dir,-Rโ Similar to--root-directory=, but uses the root directory of thesystemd-runprocess as the root directory to execute the service in.
Added in version 259.
-E NAME[=VALUE],--setenv=NAME[=VALUE]โ Runs the service process with the specified environment variable set. You can use this parameter more than once to set multiple variables. When"="and VALUE are omitted, the value of the variable with the same name in the program environment will be used.
Also see Environment= in systemd.exec(5).
Added in version 211.
--pty,-tโ When invoking the command, the transient service connects its standard input, output and error to the terminalsystemd-runis invoked on, via a pseudo TTY device. This lets you run programs that expect interactive user input/output as services โ like interactive command shells, bestie ๐
This option will result in systemd-run synchronously waiting for the transient service to terminate, similar to specifying --wait. If specified along with --wait, systemd-run will not exit when you manually disconnect from the pseudo TTY device.
Note that machinectl(1)'s shell command is usually a better alternative for requesting a new, interactive login session on the local host or a local container. Just saying ๐ซถ
See below for details on how this switch combines with --pipe.
Added in version 219.
--pty-late,-Tโ Much like--pty, but the PTY communication only begins once the unit start is complete. Effectively this means PTY input duringExecStartPre=of a service is not available while using--pty-late, while it is available using--pty, for most service types. However, if this option is used, access to the invoking TTY is available for password queries (see--no-ask-passwordbelow) during unit start-up. Trade-offs, bestie ๐ฅ
Added in version 258.
--pipe,-Pโ If specified, standard input, output, and error of the transient service are inherited from thesystemd-runcommand itself. This letssystemd-runbe used within shell pipelines. Pipes? Pipes. ๐ง
Note that this mode is not suitable for interactive command shells and similar, because the service process will not become a TTY controller when invoked on a terminal. Use --pty instead in that case.
When both --pipe and --pty are used in combination, the more appropriate option is automatically determined and used. Specifically: when invoked with standard input, output and error connected to a TTY, --pty is used, and otherwise --pipe.
This option will result in systemd-run synchronously waiting for the transient service to terminate, similar to specifying --wait.
When this option is used, the original file descriptors systemd-run receives are passed to the service processes as-is. If the service runs with different privileges than systemd-run, this means the service might not be able to reopen the passed file descriptors, due to normal file descriptor access restrictions. If the invoked process is a shell script that uses the echo "hello" >/dev/stderr construct for writing messages to stderr, this might cause problems, as this only works if stderr can be reopened. To mitigate this, use the construct echo "hello" >&2 instead, which is mostly equivalent and avoids this pitfall. Write it down bestie, this one bites people ๐
Added in version 235.
(part 1 done โ more coming, don't go anywhere ๐ฆ)
--shell,-Sโ bestie, this is the "just gimme a shell" button ๐ It's a shortcut for--pty --same-dir --wait --collect --service-type=exec $SHELL, aka you get an interactive shell, in the current working directory, running in service context, and you only had to type one (1) switch to get it. One switch!! (added in version 240 โจ)--quiet,-qโ shhh ๐คซ suppresses extra informational output while it's running. Super handy when you pair it with--pty, because that's exactly the case where it kills the initial message explaining how to terminate the TTY connection. (added in version 219 โจ)-v,--verboseโ shows you the unit log output while the thing is running. Not after, while. ๐ (added in version 258 โจ)--output=โ controls how that verbose unit log output gets formatted. Possible values live over injournalctl(1), so go check there. (added in version 261 โจ)--on-active=,--on-boot=,--on-startup=,--on-unit-active=,--on-unit-inactive=โ these define a monotonic timer relative to different starting points, for when the command you specified should start. The deets are inOnActiveSec=,OnBootSec=,OnStartupSec=,OnUnitActiveSec=andOnUnitInactiveSec=insystemd.timer(5). Each one of these is secretly a shortcut for--timer-property=with the matching property, iykyk. ๐
โ ๏ธ Heads up: these may not be combined with--scopeor--pty. Not a vibe, just the rules.
(added in version 218 โจ)
--on-calendar=โ sets up a calendar timer for starting your command. SeeOnCalendar=insystemd.timer(5). This one's a shortcut for--timer-property=OnCalendar=. ๐
Same warning as above: may not be combined with--scopeor--pty.
(added in version 218 โจ)
--on-clock-change,--on-timezone-changeโ these define a trigger based on system clock jumps or timezone changes, so your command fires when time does a little chaos dance. SeeOnClockChange=andOnTimezoneChange=insystemd.timer(5). They're shortcuts for--timer-property=OnClockChange=yesand--timer-property=OnTimezoneChange=yesrespectively. ๐ฐ๏ธ
Yep, you guessed it: may not be combined with--scopeor--pty.
(added in version 242 โจ)
--path-property=,--socket-property=,--timer-property=โ sets a property on the path, socket, or timer unit that gets created. It's giving--property=, except it applies to the transient path, socket, or timer unit instead of the transient service unit. It takes an assignment in the same format as theset-propertycommand fromsystemctl(1). ๐
And yes, once more with feeling: these may not be combined with--scopeor--pty.
(added in version 218 โจ)
--no-blockโ do not synchronously wait for the unit start operation to finish. Here's the tea: if you don't pass this, the start request for the transient unit gets verified, enqueued, and thensystemd-runsits there waiting until the unit's start-up is completed. If you do pass it, the request is only verified and enqueued, and off it goes. ๐
May not be combined with--waitor--scope.
(added in version 220 โจ)
--waitโ synchronously waits for the transient service to terminate. So the start request gets verified, enqueued, and waited for. Then the invoked unit is monitored, and it's waited on until it's deactivated again (most likely because the command you gave it finished, bestie). On exit you get terse info about the unit's runtime โ total runtime, plus CPU, memory, IO, and IP accounting data if the corresponding cgroup accounting settings are enabled โ and the exit code and status of the main process. You can mute all that with--quiet. ๐ฅ
May not be combined with--no-block,--remain-after-exit,--scope, or the various path, socket, or timer options.
(added in version 232 โจ)
-G,--collectโ unloads the transient unit after it completed, even if it failed. Because normally (i.e. without this option) every unit that ran and failed is kept in memory until you explicitly reset its failure state withsystemctl reset-failedor an equivalent command โ meanwhile units that ran successfully get unloaded immediately. Turn this on and the "garbage collection" of units gets way more aggressive, unloading units regardless of whether they exited successfully or failed. ๐๏ธ It's a shortcut for--property=CollectMode=inactive-or-failed; the full explanation ofCollectMode=is chilling insystemd.unit(5). (added in version 236 โจ)--job-mode=MODEโ when a new job gets queued, this controls how to deal with jobs that are already queued. It takes the same mode values assystemctl(1)'s--job-mode=option. The default job mode is"fail". And if you run--job-mode=helpit'll show you a list of all the available job modes. ๐ (added in version 258 โจ)--ignore-failureโ by default, if the command you specified fails, the invoked unit gets marked failed (though it might still be unloaded, see--collect=, above) and that gets reported in the logs. Pass this switch and that reporting is suppressed, and any non-success exit status/code of the command is treated as success. ๐
Not supported in --scope mode, bestie.
(added in version 256 โจ)
--background=COLORโ changes the terminal background color to the ANSI color you specify, for as long as the session lasts. ๐จ The color you give should be an ANSI X3.64 SGR background color, i.e. strings like"40","41", ...,"47","48;2;...","48;5;...". See ANSI Escape Code (Wikipedia)[1] for the full lore. (added in version 256 โจ)--userโ talks to the service manager of the calling user, instead of the service manager of the system. Your own little kingdom ๐--systemโ talks to the service manager of the system. This is the implied default, so if you don't say anything, this is what you get. ๐ฅ๏ธ-H,--host=โ execute the operation remotely. Give it a hostname, or a username and hostname separated by"@", to connect to. The hostname may optionally be suffixed by the port ssh is listening on, separated by":", and then a container name, separated by"/", which connects you straight to a specific container on that host. This uses SSH to talk to the remote machine manager instance. ๐ You can enumerate container names withmachinectl -H HOST. And put IPv6 addresses in brackets, ok? Don't forget, that's a whole thing.-M,--machine=โ execute the operation on a local container. Give it a container name to connect to, optionally prefixed by a user name to connect as, plus a separating"@"character. If you use the special string".host"in place of the container name, a connection to the local system is made โ which is useful for connecting to a specific user's user bus:--user --machine=lennart@.host. ๐ซถ If the"@"syntax isn't used, the connection is made as root user. If the"@"syntax is used, either the left hand side or the right hand side may be omitted (but not both), in which case the local user name and".host"are implied.-C,--capsule=โ execute the operation on a capsule. Give it a capsule name to connect to. ๐ง Seecapsule@.service(5)for all the details about capsules. (added in version 256 โจ)--no-ask-passwordโ do not query the user for authentication for privileged operations. No pop-ups, no drama. ๐-h,--helpโ prints a short help text and exits. Classic. ๐--versionโ prints a short version string and exits. Also classic. โญ--json=MODEโ shows output formatted as JSON. It expects one of"short"(the shortest possible output, no redundant whitespace or line breaks),"pretty"(a pretty version of that same thing, with indentation and line breaks), or"off"(turns JSON output off โ this is the default). ๐--no-pagerโ do not pipe output into a pager. Currently this only applies to--help. (The pager is not started during normal operation, so don't stress.) (added in version 258 โจ)
๐จ Important compatibility tea:--json=may not be combined with--scope,--pty,--pty-late,--pipe,--verbose, or path, socket, or timer options. Pick a lane, bestie.
Also, a lil note that matters a LOT: all command line arguments after the first non-option argument become part of the command line of the launched process. So everything after that first non-option thing? That's the launched process's business now. ๐ซก
๐ฅ EXIT STATUS ๐ฅ
Okay so, on success, 0 is returned. โจ
If systemd-run failed to start the service, you get a non-zero return value back. ๐ญ
And if systemd-run waits for the service to terminate, the return value gets propagated from the service. 0 is returned on success โ and that includes all the cases where systemd considers a service to have exited cleanly. See the discussion of SuccessExitStatus= in systemd.service(5) for the whole breakdown. ๐ซถ
๐ EXAMPLES ๐
ok bestie this is the part where it all clicks. real commands, real output, no cap. ๐ซถ
โจ Example 1. Logging environment variables provided by systemd to services
watch what happens when you just run env under systemd-run โ it gets its own unit, its own journal, its whole moment:
# systemd-run env
Running as unit: run-19945.service
# journalctl -u run-19945.service
Sep 08 07:37:21 bupkis systemd[1]: Starting /usr/bin/env...
Sep 08 07:37:21 bupkis systemd[1]: Started /usr/bin/env.
Sep 08 07:37:21 bupkis env[19948]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Sep 08 07:37:21 bupkis env[19948]: LANG=en_US.UTF-8
Sep 08 07:37:21 bupkis env[19948]: BOOT_IMAGE=/vmlinuz-3.11.0-0.rc5.git6.2.fc20.x86_64
that's it. the environment variables systemd handed to the service are just right there in the journal. iconic behavior honestly. ๐
โจ Example 2. Limiting resources available to a command
# systemd-run -p IOWeight=10 updatedb
so this one runs the updatedb(8) tool, but it lowers the block I/O weight for it down to 10. wanna know more about the IOWeight= property? go read systemd.resource-control(5). she explains it way better than me. ๐
โจ Example 3. Running commands at a specified time
the following command will touch a file after 30 seconds. yes, like a little timed surprise:
# date; systemd-run --on-active=30 --timer-property=AccuracySec=100ms /bin/touch /tmp/foo
Mon Dec 8 20:44:24 KST 2014
Running as unit: run-71.timer
Will run service as unit: run-71.service
# journalctl -b -u run-71.timer
-- Journal begins at Fri 2014-12-05 19:09:21 KST, ends at Mon 2014-12-08 20:44:54 KST. --
Dec 08 20:44:38 container systemd[1]: Starting /bin/touch /tmp/foo.
Dec 08 20:44:38 container systemd[1]: Started /bin/touch /tmp/foo.
# journalctl -b -u run-71.service
-- Journal begins at Fri 2014-12-05 19:09:21 KST, ends at Mon 2014-12-08 20:44:54 KST. --
Dec 08 20:44:48 container systemd[1]: Starting /bin/touch /tmp/foo...
Dec 08 20:44:48 container systemd[1]: Started /bin/touch /tmp/foo.
notice there's a timer unit AND a service unit. two units, one job. main character energy for real. โฐโจ
โจ Example 4. Allowing access to the tty
this one invokes bash(1) as a service, and it passes its standard input, output and error straight to the calling TTY:
# systemd-run -t --send-sighup bash
that's the whole thing. your terminal, your shell, but it's a service now. slay. ๐
โจ Example 5. Start screen as a user service
$ systemd-run --scope --user screen
Running scope as unit run-r14b0047ab6df45bfb45e7786cc839e76.scope.
$ screen -ls
There is a screen on:
492..laptop (Detached)
1 Socket in /var/run/screen/S-fatima.
okay so what's actually happening here: this starts the screen process as a child of the systemd --user process that was started by user@.service, inside a scope unit. and here's the why, bestie: a systemd.scope(5) unit is used instead of a systemd.service(5) unit, because screen will exit when detaching from the terminal, and a service unit would be terminated. ๐
running screen as a user unit has the advantage that it is not part of the session scope. and heads up โ if KillUserProcesses=yes is configured in logind.conf(5) (it defaults to "no"), the session scope will be terminated when the user logs out of that session.
the user@.service is started automatically when the user first logs in, and stays around as long as at least one login session is open. after the user logs out of the last session, user@.service and all services underneath it are terminated. this behavior is the default, when "lingering" is not enabled for that user. enabling lingering means that user@.service is started automatically during boot, even if the user is not logged in, and that the service is not terminated when the user logs out.
enabling lingering allows the user to run processes without being logged in โ for example, to allow screen to persist after the user logs out, even if the session scope is terminated. and in the default configuration, users can enable lingering for themselves:
$ loginctl enable-linger
periodt. no sudo, no drama. ๐ธ
โจ Example 6. Variable expansion by the manager
$ systemd-run -t echo "<${INVOCATION_ID}>" '<${INVOCATION_ID}>'
<> <5d0149bfa2c34b79bccb13074001eb20>
okay this one is SO satisfying, watch closely: the first argument is expanded by the shell (double quotes), but the second one is not expanded by the shell (single quotes). echo(1) is called with ["/usr/bin/echo", "<>", "<${INVOCATION_ID}>"] as the argument array, and then systemd(1) generates ${INVOCATION_ID} and substitutes it in the command-line.
and the reason this substitution could not be done on the client side? because the target ID that will be set for the service is not known before the call is made. mystery until the last second. ๐ โจ
โจ Example 7. Variable expansion and output redirection using a shell
variable expansion by systemd(1) can be disabled with --expand-environment=no.
disabling variable expansion can be useful if the command to execute contains dollar characters and escaping them would be inconvenient. for example, when a shell is used:
$ systemd-run --expand-environment=no -t bash \
-c 'echo $SHELL $$ >/dev/stdout'
/bin/bash 12345
the last argument is passed verbatim to the bash(1) shell which is started by the service unit. the shell expands "$SHELL" to the path of the shell, and "$$" to its process number, and then those strings are passed to the echo built-in and printed to standard output (which, in this case, is connected to the calling terminal). chef's kiss. ๐๐ฆ
โจ Example 8. Return value
$ systemd-run --user --wait true
$ systemd-run --user --wait -p SuccessExitStatus=11 bash -c 'exit 11'
$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no \
bash -c 'kill -SIGUSR1 $$'
those three invocations will succeed, i.e. terminate with an exit code of 0. all three. no notes. ๐โญ
๐ SEE ALSO ๐
your reading list, bestie, go forth and hyperlink-hop:
systemd(1), systemctl(1), systemd.unit(5), systemd.service(5), systemd.scope(5), systemd.slice(5), systemd.exec(5), systemd.resource-control(5), systemd.timer(5), systemd-mount(1), machinectl(1), run0(1)
๐ NOTES ๐
- ANSI Escape Code (Wikipedia) https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
and that's the whole page, bestie!! ๐ฅน๐ this beauty is from systemd 262.1, and the page itself is SYSTEMD-RUN(1). now go run something in a transient unit and tell me all about it. you're gonna do amazing. periodt. ๐ โจ
๐ฆ where this page comes from
- arch rolling: systemd 262.1-1 ยท LGPL-2.1-or-later AND CC0-1.0 AND GPL-2.0-or-later AND MIT-0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms