Run an instance on any Linux server with systemd, HTTPS, managed upgrades, and backups.
Run an always-on instance on a VPS or another Linux machine. This setup uses one instance per server, a global npm installation owned by the usine user, and systemd to start it at boot.
Prerequisites
Section titled “Prerequisites”A Linux machine with systemd and cgroup v2, SSH access, administrator privileges, and persistent storage. Install Node.js 22.22 or later and npm system-wide, with node and npm on /usr/local/bin or /usr/bin. Install Git, Bash, curl, timeout, flock, and polkit; on Debian or Ubuntu, the last tools come from coreutils, util-linux, and polkitd.
To run the supervisor, install rootless Podman, or Docker, for the usine user: each supervisor turn runs in a read-only container on this server.
You also need a GitHub account that can create and install a GitHub App. For HTTPS, use a domain with a reverse proxy or a Tailscale tailnet with MagicDNS and HTTPS enabled. Keep port 7331 reachable only through loopback.
Procedure
Section titled “Procedure”-
As an administrator, create the service user and instance folder:
Terminal window sudo useradd --create-home --shell /bin/bash usinesudo usermod --append --groups systemd-journal usinesudo install -d -o usine -g usine -m 0700 /home/usine/factorysudo -iu usineIn this
usinelogin shell, install the package without root access:Terminal window npm config set prefix /home/usine/.localnpm install --global @usine/server/home/usine/.local/bin/usine --versionexit -
As an administrator, create
/etc/systemd/system/usine.service:[Unit]Description=usine orchestratorWants=network-online.targetAfter=network-online.targetConditionPathIsDirectory=/home/usine/factory[Service]Type=execUser=usineGroup=usineWorkingDirectory=/home/usine/factoryEnvironment=PATH=/home/usine/.local/bin:/usr/local/bin:/usr/bin:/binExecStart=/home/usine/.local/bin/usine upKillMode=mixedKillSignal=SIGTERMTimeoutStopSec=1hSuccessExitStatus=130Restart=on-failureRestartSec=10sNoNewPrivileges=yes[Install]WantedBy=multi-user.targetKeep
usine upin the foreground so systemd manages its main process.KillMode=mixedlets the orchestrator drain attempts before systemd terminates remaining processes. The server reads the instance’s.envitself; store service credentials there with mode0600rather than usingEnvironmentFile=. -
Create
/etc/systemd/system/usine-upgrade.servicefor the independent upgrade helper:[Unit]Description=usine package upgrade and rollbackAfter=network-online.target[Service]Type=execUser=usineGroup=usineWorkingDirectory=/home/usine/factoryEnvironment=PATH=/home/usine/.local/bin:/usr/local/bin:/usr/bin:/binExecStart=/bin/bash /home/usine/factory/.usine/upgrade/run.shTimeoutStartSec=infinityNoNewPrivileges=yesThe orchestrator writes
run.shwhen an upgrade starts. Leave this service disabled at boot; the orchestrator starts it on demand so package replacement and rollback can continue whileusine.servicestops. -
Create
/etc/polkit-1/rules.d/50-usine.rulesto let the service user manage only these units:polkit.addRule(function (action, subject) {if (action.id === "org.freedesktop.systemd1.manage-units" &&["usine.service", "usine-upgrade.service"].indexOf(action.lookup("unit")) >= 0 &&subject.user === "usine" &&["start", "stop", "restart"].indexOf(action.lookup("verb")) >= 0) {return polkit.Result.YES;}});Then load the units and start the orchestrator:
Terminal window sudo systemctl daemon-reloadsudo systemctl enable --now usine.servicesudo journalctl -u usine.service -n 80 -
From your own machine, forward the loopback listener over SSH:
Terminal window ssh -N -L 7331:127.0.0.1:7331 your-user@server.example.comReplace
server.example.comwith your host and use your configured SSH account. In a separate SSH session, generate a sign-in link as the service user:Terminal window sudo -iu usinecd /home/usine/factory/home/usine/.local/bin/usine sign-in-linkOpen the printed link on your own machine. Complete GitHub App creation, installation, and first-admin sign-in, then select Open usine. Open the web UI explains later connections.
-
Configure remote workers and, for agents with
coordinator: session, supply the parent credential as theusineuser. If using local workers, install and authenticate their harnesses as that user and ensure the service’sPATHincludes them. The quickstart demonstrates authentication for each harness. -
Expose the instance over HTTPS using Caddy, another reverse proxy, or Tailscale Serve. Set
USINE_ORIGINin/home/usine/factory/.envto the HTTPS address, update the GitHub App callback, and restart the service. Complete first-member enrollment before sharing the address. -
Back up instance state to protected storage on another machine, including both databases and the credential files. Keep a recoverable backup before upgrading. Run
/home/usine/.local/bin/usine upgrade --checkfrom the instance folder asusine; Configuration > Settings controls proposed or automatic upgrades. The helper backs up both stores before replacing the package and restores the previous package and stores if installation or readiness fails.
Check the result
Section titled “Check the result”systemctl is-enabled usine.service prints enabled, and systemctl is-active usine.service prints active. From /home/usine/factory as usine, /home/usine/.local/bin/usine status reports usine.service as managed with systemctl. Check readiness on the server:
curl --fail http://127.0.0.1:7331/api/readyAfter startup recovery, the response is {"ok":true}. The HTTPS UI requires sign-in, and an anonymous request to /api/auth/session returns 401. Inspect journalctl -u usine.service for startup errors and journalctl -u usine-upgrade.service for upgrade outcomes. Verify that your backup contains every database file reported by usine backup, and use restore instance state to check recovery on a separate instance folder.
For a server in a Hetzner gateway’s private network, use the advanced Hetzner setup.