systemd-run: Run Services Without Unit Files
Sometimes you need to run a process under systemd’s control without writing a unit file — maybe you’re in a container without systemd, on someone else’s machine, or just need a quick one-off. That’s where systemd-run comes in.
Why systemd-run
The tool creates a transient unit — a unit that exists only in systemd’s memory, with no file on disk. This is useful when you need:
- Resource control (cgroup) over an ad-hoc process;
- The process to survive terminal closure;
- Isolation in a separate slice.
In practice, it’s a wrapper around systemctl start for units nobody persists.
Basic Syntax
Simple run:
The process lands in user.slice, runs in the background, and is manageable via systemctl. Verify:
You’ll see something like run-u1234.service in the output.
Key Flags
| Flag | Purpose |
|---|---|
--scope | Create a scope unit instead of service (parent process stays in scope) |
--unit=NAME | Assign a custom unit name instead of auto-generated |
--uid=USER | Run as the specified user |
--gid=GROUP | Run as the specified group |
--nice=N | Nice value (-20 to 19) |
--property=KEY=VALUE | Pass a unit property (CPUAccounting, MemoryMax, etc.) |
--chdir=PATH | Working directory |
--setenv=VAR=VALUE | Environment variable |
--tmpfs=PATH:OPTIONS | Mount a tmpfs at the specified path |
Flags combine freely. Example with resource limits:
Isolation: CPU, RAM, Root via cgroup
The main value is resource control through cgroup v2.
Memory limit:
If the process exceeds the limit, systemd kills it (OOM via cgroup).
CPU limit:
50% of one CPU core. On multi-core systems, calculate percentages against total units.
Isolation with alternate root:
RootDirectory requires a correct directory structure inside. Without it, the process fails to start with “Failed to pivot root”.
Mount a tmpfs:
Useful for processing temporary files with a size constraint.
Combined example:
All process resources are tracked in cgroup, visible via systemd-cgtop and /sys/fs/cgroup.
Comparison with nohup, setsid, chroot
| Tool | Resources | Isolation | Survives logout | Management |
|---|---|---|---|---|
nohup | No | No | Yes | No |
setsid | No | No | Yes | No |
chroot | No | Filesystem only | Depends | No |
systemd-run | cgroup | CPU, RAM, I/O, user | Yes | systemctl |
nohup and setsid work fine for background scripts. But if you need memory limits or CPU priority — cgroup is non-negotiable.
chroot solves only filesystem isolation. You can’t do systemd-run --chroot, but you can combine them:
Here chroot handles filesystem isolation, systemd-run limits resources.
Pitfalls: scope vs service
By default, systemd-run creates a service unit. The difference:
- service — full unit, registered with systemd, has dependencies, managed normally.
- scope — tied to the parent process. Specified with
--scope. Parent dies — scope dies.
In day-to-day practice, service works for long-lived processes:
Scope is useful for grouping related processes:
If you run with --scope and close the terminal — processes die. Without --scope — they survive.
Transient Unit Limitations
Transient units don’t persist across reboots. Obvious, but there are other gotchas:
- No dependencies. Units lack
After=,Wants=,Requires=. The process starts immediately. If you need sequencing — chain commands in a script withsleep. - Limited property set. Not all unit file fields are available via
--property. For example,Restart=alwaysis not supported in transient units (tested on systemd 254). - Harder to debug. No file — no
systemctl edit. All parameters are only in the command line.
For long-running services with dependencies and restart policies — a unit file remains the right choice. systemd-run is for one-off tasks, prototyping, and resource limiting on the fly.