# systemd-run: Run Services Without Unit Files

LLMS index: [llms.txt](/en/llms.txt)

---

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

```bash
systemd-run [OPTIONS] COMMAND [ARGUMENTS...]
```

Simple run:

```bash
systemd-run /bin/bash -c "while true; do :; done"
```

The process lands in user.slice, runs in the background, and is manageable via `systemctl`. Verify:

```bash
systemctl list-units --type=service
```

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:

```bash
systemd-run \
  --uid=appuser \
  --gid=appgroup \
  --nice=10 \
  --property=MemoryMax=512M \
  --property=CPUAccounting=true \
  --property=CPUWeight=256 \
  -- /opt/myapp/bin/server
```

## Isolation: CPU, RAM, Root via cgroup

The main value is resource control through cgroup v2.

**Memory limit:**

```bash
systemd-run \
  --property=MemoryMax=1G \
  --property=MemoryHigh=800M \
  /bin/memory-heavy-task
```

If the process exceeds the limit, systemd kills it (OOM via cgroup).

**CPU limit:**

```bash
systemd-run \
  --property=CPUQuota=50% \
  /bin/calculator
```

50% of one CPU core. On multi-core systems, calculate percentages against total units.

**Isolation with alternate root:**

```bash
systemd-run \
  --property=RootDirectory=/opt/jail \
  --property=RootImage=overlayfs \
  /bin/sh -c "whoami"
```

> [!WARNING]  
> `RootDirectory` requires a correct directory structure inside. Without it, the process fails to start with "Failed to pivot root".

**Mount a tmpfs:**

```bash
systemd-run \
  --tmpfs=/tmp/isolated:rw,noexec,size=256M \
  -- /bin/sh
```

Useful for processing temporary files with a size constraint.

**Combined example:**

```bash
systemd-run \
  --uid=nobody \
  --gid=nogroup \
  --nice=5 \
  --chdir=/var/data \
  --property=MemoryMax=256M \
  --property=CPUQuota=25% \
  --property=IOAccounting=true \
  -- /usr/local/bin/worker --queue=default
```

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:

```bash
systemd-run \
  --uid=nobody \
  --property=MemoryMax=100M \
  --chdir=/tmp \
  /usr/sbin/chroot --userspec=nobody /opt/jail /bin/daemon
```

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:

```bash
systemd-run --unit=my-daemon /usr/local/bin/daemon
```

Scope is useful for grouping related processes:

```bash
systemd-run --scope --uid=user1 /bin/bash -c 'spawn-worker-1 & spawn-worker-2 & wait'
```

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 with `sleep`.
- **Limited property set.** Not all unit file fields are available via `--property`. For example, `Restart=always` is 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.
