- syncup: podman first, docker only as fallback (podman wins on name clash;
existing docker units found in podman switch to podman)
- syncup: group podman-compose containers into one `kind: stack` unit per
project (project, working_dir, compose_files, compose_bin from labels);
disable existing member units ("part of stack <project>")
- generator: compose mode (podman-compose up/stop, WorkingDirectory) with
pod start/stop fallback when compose binary/dir/files are missing
- reconcile: stacks disabled with "stack not found" when neither compose
files nor pod exist
- root: scan root + rootless users listed in `users:` (podman only)
- non-root: config ~/.config/unitdore/units.yaml, own user units only
- sysuser package: runuser with XDG_RUNTIME_DIR/DBUS env, home/linger lookup
- install: warn when linger is not enabled for user units
- testdata/stack: podman-compose test stack and captured podman ps fixture
- docs: README, generated-units, man page, AGENTS, PLAN
5.6 KiB
unitdore
A door you open and close for container units.
Unitdore bridges your container runtime (Podman, Docker) and systemd. It discovers running containers and podman-compose stacks, stores them in a config file, and generates + manages systemd .service units 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
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
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
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