Files
unitdore/docs/generated-units.md
T
warkanum df9b226c7e feat: OpenRC and runit init backends behind an Init interface
Extract init-neutral unit resolution into service/, add initsys.Init with systemd, openrc and runit backends, selected by 'init:' config or autodetect. OpenRC and runit are root only; rootless units run as system services as that user. Add docs and generated examples for all three.
2026-10-12 20:19:58 +02:00

13 KiB

Generated Unit Files (systemd, OpenRC, runit)

Unitdore generates .service files from your units.yaml config. This document shows every combination with annotated examples.


System Unit (root, no user)

Config:

units:
  - name: nginx
    runtime: podman
    order: 1
    enabled: true

Written to: /etc/systemd/system/unitdore-nginx.service

# /etc/systemd/system/unitdore-nginx.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: nginx (podman)
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a nginx
ExecStop=/usr/bin/podman stop nginx
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Notes:

  • After=network.target ensures the network is up before starting
  • WantedBy=multi-user.target means it starts in normal boot runlevel
  • Restart=on-failure restarts the container if it exits unexpectedly

System Unit (Docker)

Config:

units:
  - name: redis
    runtime: docker
    order: 1
    enabled: true

Written to: /etc/systemd/system/unitdore-redis.service

# /etc/systemd/system/unitdore-redis.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: redis (docker)
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/docker start redis
ExecStop=/usr/bin/docker stop redis
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Notes:

  • Docker uses docker start <name> (no -a flag needed)

User Unit (rootless container)

Config:

units:
  - name: myapp
    runtime: podman
    user: hein
    order: 2
    enabled: true

Written to: /home/hein/.config/systemd/user/unitdore-myapp.service

# ~/.config/systemd/user/unitdore-myapp.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: myapp (podman)
After=default.target

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a myapp
ExecStop=/usr/bin/podman stop myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target

Notes:

  • User units use After=default.target and WantedBy=default.target
  • The file is written to the user's own systemd directory
  • Unitdore calls runuser -u hein -- systemctl --user for enable/start/status

Custom Command

Config:

units:
  - name: myscript
    runtime: podman
    command: /opt/myapp/start.sh
    order: 3
    enabled: true

Written to: /etc/systemd/system/unitdore-myscript.service

# /etc/systemd/system/unitdore-myscript.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: myscript (podman)
After=network.target

[Service]
Type=simple
ExecStart=/opt/myapp/start.sh
ExecStop=
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Notes:

  • When command is set, it is used verbatim as ExecStart
  • ExecStop is left empty — systemd will send SIGTERM to the process group

With Prefix and Suffix

Config:

prefix: prod-
suffix: -svc

units:
  - name: nginx
    runtime: podman
    order: 1
    enabled: true
  - name: api
    runtime: docker
    order: 2
    enabled: true

Written to:

  • /etc/systemd/system/unitdore-prod-nginx-svc.service
  • /etc/systemd/system/unitdore-prod-api-svc.service
# /etc/systemd/system/unitdore-prod-nginx-svc.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: nginx (podman)
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a nginx
ExecStop=/usr/bin/podman stop nginx
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Notes:

  • prefix and suffix apply to the service file name only — the container name stays unchanged
  • Useful for distinguishing environments (prod-, staging-) or roles (-web, -worker)

Container Named by Short ID

If a container has no name (e.g. started with docker run without --name), unitdore uses its short container ID (first 12 characters) as the unit name.

Example: Container ID a3f2c1b9e4d7 with no name →

Config entry added by syncup:

units:
  - name: a3f2c1b9e4d7
    runtime: docker
    order: 1
    enabled: true

Written to: /etc/systemd/system/unitdore-a3f2c1b9e4d7.service

Tip: After syncup, use unitdore edit to give it a proper name before running install.


Startup Ordering Example

Config:

units:
  - name: postgres
    runtime: podman
    order: 1
    enabled: true

  - name: redis
    runtime: podman
    order: 1         # same order = starts in parallel
    enabled: true

  - name: api
    runtime: podman
    order: 2         # waits until order 1 group is started
    enabled: true

  - name: nginx
    runtime: podman
    order: 3
    enabled: true

unitdore active starts units in ascending order value, lowest first. Units with the same order are started together (parallel).


Disabled Unit

Config:

units:
  - name: oldthing
    runtime: podman
    order: 1
    enabled: false
    disabled_reason: container not found
  • unitdore install will remove the .service file if it exists
  • unitdore active will skip disabled units
  • unitdore status shows the reason in the REASON column

Stack Unit (podman-compose, compose mode)

Used when compose_bin, working_dir and all compose_files exist.

- name: shop
  kind: stack
  runtime: podman
  project: shop
  working_dir: /srv/shop
  compose_files: [compose.yaml]
  compose_bin: /usr/bin/podman-compose
  enabled: true
# /etc/systemd/system/unitdore-shop.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: shop (podman-compose stack)
After=network.target

[Service]
Type=simple
WorkingDirectory=/srv/shop
ExecStart=/usr/bin/podman-compose -p shop -f compose.yaml up
ExecStop=-/usr/bin/podman-compose -p shop -f compose.yaml stop

[Install]
WantedBy=multi-user.target

Stack Unit (pod fallback)

Used when the compose binary, working dir or a compose file is missing. Requires pod pod_<project>.

[Unit]
Description=Unitdore: shop (podman pod)
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/podman pod start pod_shop
ExecStop=-/usr/bin/podman pod stop pod_shop

Config Behind the Init System Examples

init: systemd   # systemd | openrc | runit
units:
  - {name: nginx,  runtime: podman, order: 1, enabled: true}
  - {name: redis,  runtime: docker, order: 2, enabled: true, restart: true, restart_sec: 5, restart_retries: 3}
  - {name: myapp,  runtime: podman, user: alice, order: 3, enabled: true}
  - {name: backup, runtime: script, script: /opt/scripts/backup.sh, stop_script: /opt/scripts/stop.sh, shell: /bin/sh, working_dir: /opt/scripts, order: 4, enabled: true}
  - {name: agent,  runtime: exec, executable: /opt/agent/bin/agent, args: [--config, /etc/agent.toml], service_type: oneshot, order: 5, enabled: true}
  - {name: shop,   runtime: podman, kind: stack, project: shop, order: 6, enabled: true}

Script and Executable Units (systemd)

script runs <shell> <script> (default shell /bin/bash); exec runs the binary directly.

# /etc/systemd/system/unitdore-backup.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: backup (sh script)
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/scripts
ExecStart=/bin/sh /opt/scripts/backup.sh
ExecStop=-/bin/sh /opt/scripts/stop.sh

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/unitdore-agent.service
# Generated by unitdore — do not edit manually

[Unit]
Description=Unitdore: agent (executable)
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/agent/bin/agent --config /etc/agent.toml
ExecStop=

[Install]
WantedBy=multi-user.target

OpenRC

Root only. Files in /etc/init.d/. Rootless user: units run as a system service via command_user.

nginx

#!/sbin/openrc-run
# /etc/init.d/unitdore-nginx
# Generated by unitdore — do not edit manually

description="Unitdore: nginx (podman)"

command_background=yes
pidfile="/run/${RC_SVCNAME}.pid"
command="/usr/bin/podman"
command_args="start -a nginx"

depend() {
	need net
}

stop_pre() {
	/usr/bin/podman stop nginx || true
}

redis

#!/sbin/openrc-run
# /etc/init.d/unitdore-redis
# Generated by unitdore — do not edit manually

description="Unitdore: redis (docker)"

supervisor=supervise-daemon
respawn_delay=5
respawn_max=3
command="/usr/bin/docker"
command_args="start -a redis"

depend() {
	need net
	need docker
}

stop_pre() {
	/usr/bin/docker stop redis || true
}

myapp

#!/sbin/openrc-run
# /etc/init.d/unitdore-myapp
# Generated by unitdore — do not edit manually

description="Unitdore: myapp (podman)"
export HOME=/home/alice
export XDG_RUNTIME_DIR=/run/user/1000

command_background=yes
pidfile="/run/${RC_SVCNAME}.pid"
command="/usr/bin/podman"
command_args="start -a myapp"
command_user="alice"

depend() {
	need net
}

start_pre() {
	checkpath -d -m 0700 -o alice /run/user/1000
}

stop_pre() {
	su -m -s /bin/sh -c '/usr/bin/podman stop myapp' alice || true
}

backup

#!/sbin/openrc-run
# /etc/init.d/unitdore-backup
# Generated by unitdore — do not edit manually

description="Unitdore: backup (sh script)"

command_background=yes
pidfile="/run/${RC_SVCNAME}.pid"
command="/bin/sh"
command_args="/opt/scripts/backup.sh"
directory="/opt/scripts"

depend() {
	need net
}

stop_pre() {
	/bin/sh /opt/scripts/stop.sh || true
}

agent

#!/sbin/openrc-run
# /etc/init.d/unitdore-agent
# Generated by unitdore — do not edit manually

description="Unitdore: agent (executable)"

start() {
	ebegin "Starting $RC_SVCNAME"
	/opt/agent/bin/agent --config /etc/agent.toml
	eend $?
}

depend() {
	need net
}

shop

#!/sbin/openrc-run
# /etc/init.d/unitdore-shop
# Generated by unitdore — do not edit manually

description="Unitdore: shop (podman pod)"

start() {
	ebegin "Starting $RC_SVCNAME"
	/usr/bin/podman pod start pod_shop
	eend $?
}

stop() {
	ebegin "Stopping $RC_SVCNAME"
	/usr/bin/podman pod stop pod_shop
	eend $?
}

depend() {
	need net
}

runit

Root only. Each unit is /etc/sv/unitdore-<name>/{run,finish} symlinked into the service dir. Install adds a down file (no autostart until started/enabled). Both files are shown per unit.

nginx

# ===== /etc/sv/unitdore-nginx/run =====
#!/bin/sh
# unitdore-nginx — generated by unitdore, do not edit manually
exec 2>&1
exec /usr/bin/podman start -a nginx

# ===== /etc/sv/unitdore-nginx/finish =====
#!/bin/sh
# unitdore-nginx — generated by unitdore, do not edit manually
/usr/bin/podman stop nginx || true
exec sv down .

redis

# ===== /etc/sv/unitdore-redis/run =====
#!/bin/sh
# unitdore-redis — generated by unitdore, do not edit manually
exec 2>&1
exec /usr/bin/docker start -a redis

# ===== /etc/sv/unitdore-redis/finish =====
#!/bin/sh
# unitdore-redis — generated by unitdore, do not edit manually
/usr/bin/docker stop redis || true
[ "$1" = 0 ] && exec sv down .
sleep 5

myapp

# ===== /etc/sv/unitdore-myapp/run =====
#!/bin/sh
# unitdore-myapp — generated by unitdore, do not edit manually
exec 2>&1
[ -d /run/user/1000 ] || install -d -m 0700 -o alice /run/user/1000
export HOME=/home/alice
export XDG_RUNTIME_DIR=/run/user/1000
exec chpst -u alice /usr/bin/podman start -a myapp

# ===== /etc/sv/unitdore-myapp/finish =====
#!/bin/sh
# unitdore-myapp — generated by unitdore, do not edit manually
chpst -u alice /usr/bin/podman stop myapp || true
exec sv down .

backup

# ===== /etc/sv/unitdore-backup/run =====
#!/bin/sh
# unitdore-backup — generated by unitdore, do not edit manually
exec 2>&1
cd /opt/scripts || exit 1
exec /bin/sh /opt/scripts/backup.sh

# ===== /etc/sv/unitdore-backup/finish =====
#!/bin/sh
# unitdore-backup — generated by unitdore, do not edit manually
cd /opt/scripts 2>/dev/null
/bin/sh /opt/scripts/stop.sh || true
exec sv down .

agent

# ===== /etc/sv/unitdore-agent/run =====
#!/bin/sh
# unitdore-agent — generated by unitdore, do not edit manually
exec 2>&1
/opt/agent/bin/agent --config /etc/agent.toml || exit 1
exec tail -f /dev/null

# ===== /etc/sv/unitdore-agent/finish =====
#!/bin/sh
# unitdore-agent — generated by unitdore, do not edit manually
exec sv down .

shop

# ===== /etc/sv/unitdore-shop/run =====
#!/bin/sh
# unitdore-shop — generated by unitdore, do not edit manually
exec 2>&1
/usr/bin/podman pod start pod_shop || exit 1
exec tail -f /dev/null

# ===== /etc/sv/unitdore-shop/finish =====
#!/bin/sh
# unitdore-shop — generated by unitdore, do not edit manually
/usr/bin/podman pod stop pod_shop || true
exec sv down .

Quick Reference: Service File Locations

Scenario Path
System unit (root) /etc/systemd/system/unitdore-<name>.service
User unit /home/<user>/.config/systemd/user/unitdore-<name>.service
With prefix prod- /etc/systemd/system/unitdore-prod-<name>.service
With suffix -svc /etc/systemd/system/unitdore-<name>-svc.service
With both /etc/systemd/system/unitdore-prod-<name>-svc.service
OpenRC /etc/init.d/unitdore-<name>
runit /etc/sv/unitdore-<name>/ (+ symlink in /var/service or /etc/service)