Skip to content
Systemd

Introduction

The systemd provider starts units when a request needs them, and stops them when their session expires. Sablier runs on the same host and talks to the service manager over its D-Bus API. It reads its settings from an [X-Sablier] section in each unit file.

Install Sablier

Run Sablier on the host, as a service of the manager whose units it controls. Sablier reads each unit file at the path systemd reports, so it needs the host’s file system.

Download the binary from the releases page. The command fetches the amd64 build. On ARM, use arm64 or armv7 instead:

curl -fsSL https://github.com/sablierapp/sablier/releases/download/v1.19.0/sablier-1.19.0-linux-amd64.tar.gz \
  | sudo tar -xz -C /usr/local/bin sablier

Select the systemd provider in /etc/sablier/sablier.yaml:

provider:
  name: systemd
  systemd:
    # Optional: only consider units whose name matches one of these patterns.
    # Remove to consider every loaded unit.
    unit-patterns:
      - "*.service"
server:
  port: 10000

Run Sablier as a system service:

[Unit]
Description=Sablier
Documentation=https://sablierapp.dev/providers/systemd/
Wants=network-online.target
After=network-online.target

[Service]
# Runs as root: starting and stopping system units needs that privilege.
ExecStart=/usr/local/bin/sablier start --configFile /etc/sablier/sablier.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo install -D -m 0644 sablier.yaml /etc/sablier/sablier.yaml
sudo install -m 0644 sablier.service /etc/systemd/system/sablier.service
sudo systemctl daemon-reload
sudo systemctl enable --now sablier.service

Sablier connects to the system D-Bus. Starting, stopping and throttling units needs root, or a polkit rule that grants org.freedesktop.systemd1.manage-units.

Reverse proxy plugins on the same host reach Sablier at http://localhost:10000.

Usage example

Opt a unit in with an [X-Sablier] section, and give it a group:

[Unit]
Description=Sablier demo service (systemd provider)

[Service]
ExecStart=/usr/bin/sleep infinity

[X-Sablier]
Enable=true
Group=demo

[Install]
WantedBy=default.target

Install it, reload systemd, and enable the unit without starting it:

sudo install -m 0644 demo.service /etc/systemd/system/demo.service
sudo systemctl daemon-reload
sudo systemctl enable demo.service

An enabled unit stays loaded, so Sablier finds it in its group. With the user instance, install it in ~/.config/systemd/user/ and use systemctl --user without sudo.

Then configure your reverse proxy to call Sablier for the demo group in front of your service. On the first request, Sablier starts demo.service and holds the request, or shows a waiting page, until systemd reports the unit active. When the session expires, Sablier stops it.

To try it without a reverse proxy, request the group from the Sablier API:

curl "http://localhost:10000/api/strategies/blocking?group=demo&session_duration=1m"
Sablier stops opted-in units that are active when it starts. A unit you start afterwards keeps running until its first session expires, unless you stop externally-started instances.

Provider reference

Configuration

OptionDefaultDescription
--provider.systemd.user-instancefalseManage the units of the user manager of the user Sablier runs as, instead of the system manager.
--provider.systemd.unit-patterns[]Only consider units whose name matches one of these glob patterns, such as app-*.service.

Every --provider.* option that is not provider-specific also applies; see the CLI reference.

unit-patterns narrows which units Sablier lists and watches:

  • A matching unit still needs Enable=true. Patterns do not opt units in.
  • Patterns make listing cheaper. systemd filters the units, so Sablier reads fewer unit files.
  • Patterns do not filter requests by instance name. A request for a unit outside the patterns still starts it if it has Enable=true.

Workloads

Units

Sablier starts and stops units through the manager’s D-Bus API, as systemctl start and systemctl stop do. Most of the time, the unit is a service.

  • Start applies the active resource limits, if any, then starts the unit. Sablier waits until systemd finishes the start, and reports an error if it fails.
  • Stop stops the unit and waits until systemd finishes. With IdleReplicas=1, Sablier applies the idle limits instead and leaves the unit running.

Instance names

A reverse proxy can target one unit by name instead of a group. The unit of the usage example is named:

demo.service
  • Use the full unit name, with its suffix. Sablier sends the name to systemd as is: unlike systemctl, it does not add .service.
  • The name refers to the manager Sablier talks to. With user-instance, it is a unit of the user manager.

Labels

Systemd units have no labels. Sablier reads its settings from an [X-Sablier] section in the unit file:

[X-Sablier]
Enable=true
Group=demo
  • Use the exact key names below. Keys are case-sensitive, and Sablier ignores any other key.
  • systemd ignores the section. It skips sections whose name starts with X-, so the unit loads without warnings.
  • Set each key once. If a key repeats, the last value wins. List several values on one line, separated by commas.
  • Put the section in the unit file itself. Sablier reads the file systemd reports as the unit’s FragmentPath, never its drop-ins.
  • Sablier picks up [X-Sablier] changes on its own. It reads the unit file again when the file changes. Run systemctl daemon-reload for changes to the other sections.

The key names do not follow the labels’ casing, so use this table:

Label[X-Sablier] key
sablier.enableEnable
sablier.groupGroup
sablier.ready-afterReadyAfter
sablier.ready-on-startReadyOnStart
sablier.running-hoursRunningHours
sablier.running-daysRunningDays
sablier.anti-affinityAntiAffinity
sablier.idle.replicas, sablier.active.replicasIdleReplicas, ActiveReplicas
sablier.idle.cpu, sablier.active.cpuIdleCPU, ActiveCPU
sablier.idle.memory, sablier.active.memoryIdleMemory, ActiveMemory
sablier.idle.blkio-weight, sablier.active.blkio-weightIdleBlkioWeight, ActiveBlkioWeight
sablier.idle.blkio-weight-device, sablier.active.blkio-weight-deviceIdleBlkioWeightDevice, ActiveBlkioWeightDevice
sablier.idle.blkio-device-read-bps, sablier.active.blkio-device-read-bpsIdleBlkioDeviceReadBps, ActiveBlkioDeviceReadBps
sablier.idle.blkio-device-write-bps, sablier.active.blkio-device-write-bpsIdleBlkioDeviceWriteBps, ActiveBlkioDeviceWriteBps
sablier.idle.blkio-device-read-iops, sablier.active.blkio-device-read-iopsIdleBlkioDeviceReadIOps, ActiveBlkioDeviceReadIOps
sablier.idle.blkio-device-write-iops, sablier.active.blkio-device-write-iopsIdleBlkioDeviceWriteIOps, ActiveBlkioDeviceWriteIOps

The label reference describes every setting.

Workload settings

Each excerpt below is the [X-Sablier] section of a complete unit file in examples/systemd.

Enable
Since v1.18.0

Enable=true opts the unit in. It is required, and any other value is ignored. Sablier refuses to start or stop a unit without it, so a request cannot control unrelated host services. See the usage example.

Group
Since v1.18.0

Group puts the unit in one or more groups, separated by commas. A session for any of them starts it. Without it, the unit is in the default group.

[X-Sablier]
Enable=true
Group=team-a,team-b

Learn more about groups.

Ready after
Since v1.18.0

ReadyAfter keeps reporting the unit as not ready for a settling delay after systemd first reports it active. The value is a Go duration, such as 30s.

[X-Sablier]
Enable=true
Group=ready-after
ReadyAfter=30s

Use it when your service is active before it can serve traffic. With Type=simple or Type=exec, a service is active as soon as its process starts.

Learn more about delaying readiness.

Ready on start
Since v1.18.0

ReadyOnStart=true reports the unit ready as soon as Sablier asks systemd to start it, without waiting for it to be active.

[X-Sablier]
Enable=true
Group=ready-on-start
ReadyOnStart=true

Learn more about marking ready on start.

Running hours
Since v1.18.0

  • RunningHours keeps the unit running during a daily window.
  • RunningDays limits that window to some weekdays.
[X-Sablier]
Enable=true
Group=running-hours
RunningHours=09:00-18:00
RunningDays=Mon,Tue,Wed,Thu,Fri

The window uses Sablier’s local time: the host’s time zone, unless you set TZ in the Environment= of Sablier’s unit.

Learn more about running hours.

Anti-affinity
Since v1.18.0

AntiAffinity lists groups the unit backs off from, separated by commas. Sablier keeps it idle while any of them has an active session.

[X-Sablier]
Enable=true
Group=batch
AntiAffinity=streaming

Learn more about anti-affinity.

Scale instead of stop
Since v1.18.0

Keep the unit running when idle, with lower limits, instead of stopping it:

  • IdleReplicas=1 keeps the unit running when idle.
  • IdleCPU and IdleMemory set its CPU and memory limits while idle.
  • ActiveCPU and ActiveMemory set them for the next session.
[X-Sablier]
Enable=true
Group=scale-mode
# Keep the unit running when the session expires.
IdleReplicas=1
# CPU in cores: 0.1 is CPUQuota=10%, 2 is CPUQuota=200%.
IdleCPU=0.1
ActiveCPU=2
# Memory in Docker units, applied as MemoryMax=.
IdleMemory=64m
ActiveMemory=512m

Sablier sets the limits as resource control properties of the unit:

  • CPU is in decimal cores and becomes CPUQuota=. 0.1 is 10%, 2 is 200%.
  • Memory uses Docker units and becomes MemoryMax=. For example, 64m or 1g.
  • The running unit keeps running. systemd applies the new limits immediately, without a restart.
  • Limits are runtime properties. Sablier sets them as systemctl set-property --runtime does: your unit file does not change, and a reboot drops them.
  • A limit set on one side only is reset on the other. If you set IdleMemory alone, the next session sets MemoryMax=infinity, and the next expiry sets it back. If your unit file sets a limit, repeat it in the matching Active key.
  • Active limits apply on every start, even when IdleReplicas is 0 and the unit is stopped when idle.
  • One unit runs at most. IdleReplicas and ActiveReplicas above 1 count as 1, and Sablier logs a warning.

Learn more about scaling instead of stopping.

Block I/O
Since v1.18.0

With IdleReplicas=1, throttle the unit’s block I/O while idle:

[X-Sablier]
Enable=true
Group=blkio
# Keep the unit running when the session expires.
IdleReplicas=1
# Relative weight (10-1000), applied as IOWeight=.
IdleBlkioWeight=100
ActiveBlkioWeight=500
# Per-device limits: path:value, comma-separated for several devices.
IdleBlkioDeviceReadBps=/dev/sda:10m
IdleBlkioDeviceWriteBps=/dev/sda:10m
IdleBlkioDeviceReadIOps=/dev/sda:100
IdleBlkioDeviceWriteIOps=/dev/sda:100

Each key pair sets one resource control property:

Keyssystemd propertyValue
IdleBlkioWeight, ActiveBlkioWeightIOWeight=Integer from 10 to 1000
IdleBlkioWeightDevice, ActiveBlkioWeightDeviceIODeviceWeight=path:weight, weight from 10 to 1000
IdleBlkioDeviceReadBps, ActiveBlkioDeviceReadBpsIOReadBandwidthMax=path:rate, rate in Docker units
IdleBlkioDeviceWriteBps, ActiveBlkioDeviceWriteBpsIOWriteBandwidthMax=path:rate, rate in Docker units
IdleBlkioDeviceReadIOps, ActiveBlkioDeviceReadIOpsIOReadIOPSMax=path:iops, iops an integer
IdleBlkioDeviceWriteIOps, ActiveBlkioDeviceWriteIOpsIOWriteIOPSMax=path:iops, iops an integer
  • Separate devices with commas. For example, /dev/sda:10m,/dev/sdb:5m.
  • Sablier ignores a weight outside 10-1000. systemd accepts up to 10000, and its default is 100.
  • Rates use Docker units, in bytes per second. 10m is 10 MiB/s.
  • Omitted per-device limits are cleared on the switch. Sablier replaces the whole device list, so the idle limits above are removed when a session starts.
  • These are runtime properties, like the CPU and memory limits.

Learn more about throttling block I/O.

Readiness

Sablier reports a unit from its ActiveState:

  • ready when it is active.
  • starting when it is activating, reloading or refreshing.
  • stopped when it is inactive, deactivating or maintenance.
  • error when it is failed.

When a service becomes active depends on its Type=. With Type=notify, it is active once it reports ready through sd_notify. With Type=simple, it is active as soon as its process starts: add a ready-after delay if it needs time to serve traffic.

Limitations

  • Sablier must run on the host of the units it manages: it reads their unit files from disk.
  • One Sablier manages either the system manager or one user manager, not both.
  • Groups only contain units systemd has loaded. Enable a unit so it stays loaded.
  • Sablier ignores [X-Sablier] sections in drop-in files.
  • At most one instance of a unit runs: replica values above 1 count as 1.
  • Resource limits need the unified cgroup hierarchy. On a user instance, they only apply for the controllers delegated to the user manager.
  • Sablier polls systemd every 10 seconds for units started or stopped outside Sablier.

Examples

  • examples/systemd: every unit file on this page, with a Makefile to run them on your user instance.