Move version bumping into scripts/bump-version.sh, rename the ebuild per release, reset the Void checksum, and add make pkg-checksums to fill it after the release.
unitdore
A door you open and close for container units.
Unitdore bridges your container runtime (Podman, Docker) and your init system (systemd, OpenRC, runit). It discovers running containers and podman-compose stacks, stores them in a config file, and generates + manages a service for each one.
Install
Arch Linux (AUR)
yay -S unitdore
Debian / Ubuntu
Download the .deb from the releases page and install:
sudo dpkg -i unitdore_*.deb
CentOS / AlmaLinux / Rocky Linux
Download the .rpm from the releases page and install:
sudo rpm -i unitdore-*.rpm
# or with dnf:
sudo dnf install unitdore-*.rpm
Gentoo (OpenRC / systemd)
Ebuild in pkg/gentoo/app-containers/unitdore/. Copy it into a local overlay, then:
ebuild unitdore-<ver>.ebuild manifest # once per version
sudo emerge app-containers/unitdore
Void Linux (runit)
Template in pkg/void/srcpkgs/unitdore/. Copy it into a void-packages checkout, then:
./xbps-src pkg unitdore
sudo xbps-install --repository=hostdir/binpkgs unitdore
Set init: runit / init: openrc in the config if autodetect picks the wrong one.
From source
make build
sudo make install
Or manually:
go build -o unitdore .
sudo cp unitdore /usr/local/bin/
Usage
Discover running containers
sudo unitdore syncup
Queries Podman and Docker for running containers, adds new ones to the config, and reconciles any that have disappeared (marking them disabled).
- Podman first; Docker only for names Podman doesn't have
- podman-compose containers → one
kind: stackunit per project; existing member units disabled (part of stack <project>) - Root: root's containers + rootless Podman containers of each user in
users: - Normal user: own Podman containers, config
~/.config/unitdore/units.yaml, user units only
Edit the config
sudo unitdore edit
Opens /etc/unitdore/units.yaml in $EDITOR (falls back to vi).
Install systemd unit files
sudo unitdore install
Generates .service files for all enabled units and writes them to systemd. Use --dry-run to preview without writing.
sudo unitdore install --dry-run
Enable and start units
sudo unitdore active
Runs systemctl enable --now for all installed, enabled units in startup order.
Check status
sudo unitdore status
Prints a summary table:
NAME RUNTIME USER ENABLED INSTALLED STATE REASON
nginx podman root yes yes active
myapp docker hein yes yes active
oldthing podman hein no no — container not found
Config file
Location: /etc/unitdore/units.yaml
init: "" # optional: systemd | openrc | runit (empty = autodetect)
service_dir: "" # optional: runit supervised dir (empty = /var/service or /etc/service)
prefix: "" # optional: prepended to all service names, e.g. "prod-"
suffix: "" # optional: appended to all service names, e.g. "-svc"
users: [hein] # root only: rootless users scanned by syncup
units:
- name: nginx
runtime: podman # podman | docker
user: "" # empty = root/system unit
command: "" # optional: override ExecStart entirely
order: 1 # startup order (lower = earlier)
delay: 0s # delay after previous order group
enabled: true
disabled_reason: "" # auto-set by syncup reconciliation
- name: myapp
runtime: docker
user: hein # rootless: generates user unit for this user
order: 2
delay: 5s
enabled: true
- name: shop # = compose project name
kind: stack # container (default) | stack
runtime: podman
user: hein
project: shop
working_dir: /srv/shop # from compose labels (refreshed by syncup)
compose_files: [compose.yaml]
compose_bin: /usr/bin/podman-compose
order: 3
enabled: true
- name: backup # shell script unit
runtime: script
script: /opt/scripts/backup.sh # required
stop_script: /opt/scripts/stop.sh # optional → ExecStop
shell: /bin/bash # optional, default /bin/bash
service_type: simple # simple (default) | oneshot
working_dir: /opt/scripts # optional
order: 4
enabled: true
- name: agent # executable unit
runtime: exec
executable: /opt/agent/bin/agent # required
args: [--config, /etc/agent.toml] # optional
stop_executable: /opt/agent/bin/stop # optional → ExecStop
order: 5
enabled: true
Init systems
Set init: in the config, or leave empty to autodetect (/run/systemd/system → systemd, /run/openrc → openrc, /etc/runit → runit, else systemd).
| systemd | openrc | runit | |
|---|---|---|---|
| Service file | /etc/systemd/system/unitdore-<n>.service |
/etc/init.d/unitdore-<n> |
/etc/sv/unitdore-<n>/{run,finish} + symlink in service_dir |
| Start / stop | systemctl |
rc-service |
sv |
| Enable at boot | systemctl enable |
rc-update add … default |
remove down file |
| Installed state | disabled until enabled | not in runlevel | symlinked with down file (no autostart) |
Rootless user: |
systemctl --user unit (needs linger) |
system service, command_user |
system service, chpst -u |
| Non-root use | yes (own user units) | root only | root only |
restart* |
Restart=on-failure, StartLimitBurst |
supervise-daemon respawn_* (always respawns) |
finish: on-failure, RestartSec; no retry limit |
docker dependency |
Requires=docker.service |
need docker |
none |
oneshot |
RemainAfterExit |
start()/stop() functions |
run once, then tail -f /dev/null |
- Rootless units on openrc/runit set
HOMEandXDG_RUNTIME_DIR=/run/user/<uid>and create that dir if missing. command/argsuse systemd quoting; they are re-quoted for sh on openrc/runit.- No log redirection is generated on openrc; use the runtime's own logs.
docs/generated-units.mdshows systemd output only.
Script / executable units
Added manually (unitdore edit); syncup never creates or disables them.
| Key | Runtime | Meaning |
|---|---|---|
script |
script | Required. ExecStart=<shell> <script> |
stop_script |
script | Optional. ExecStop=-<shell> <stop_script> |
shell |
script | Interpreter, default /bin/bash |
executable |
exec | Required. ExecStart=<executable> <args> |
args |
exec | Optional list, quoted as needed |
stop_executable |
exec | Optional. ExecStop=-<stop_executable> |
service_type |
both | simple (default) or oneshot (RemainAfterExit=yes) |
working_dir |
both | Optional WorkingDirectory |
command |
both | If set, overrides ExecStart |
user |
both | Set for a user unit |
Stacks (podman-compose)
| Condition | Unit |
|---|---|
compose_bin, working_dir, compose_files exist |
podman-compose -p <proj> -f <files> up / stop, Type=simple |
| any missing | podman pod start/stop pod_<proj>, Type=oneshot |
| compose missing and no pod | syncup disables: stack not found |
Users
| Run as | Config | Discovers | Units |
|---|---|---|---|
| root | /etc/unitdore/units.yaml |
root + users: (Podman only for users) |
system + user units |
| user | ~/.config/unitdore/units.yaml |
own Podman | own user units |
User units need linger to start at boot: sudo loginctl enable-linger <user> (install warns).
Prefix and suffix
Set prefix and/or suffix to namespace your service files:
prefix: prod-
suffix: ""
Generates: unitdore-prod-nginx.service, unitdore-prod-myapp.service, etc.
Prefix/suffix apply to the service file name only — container names are unchanged.
Unnamed containers
If a container has no name, syncup uses its short container ID (first 12 chars) as the unit name.
You can rename it later with unitdore edit.
Generated unit files
See docs/generated-units.md for full examples covering:
- System units (root)
- User units (rootless)
- Docker vs Podman
- Custom commands
- Compose stacks (compose + pod fallback)
- Prefix/suffix naming
- Startup ordering
- Unnamed containers (short ID fallback)
Quick example:
# /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
Flags
--config string path to units config file (default "/etc/unitdore/units.yaml"; non-root: ~/.config/unitdore/units.yaml)
Requirements
- Go 1.21+
- systemd
- Podman and/or Docker (only what you have installed is used)
- podman-compose for compose-mode stacks (pod fallback without it)
- Root for system units; the relevant user for rootless units