Self-Hosting Lycato
Status: alpha, single tenant, source install. Lycato currently ships the local
lycato servesupervisor, but it does not ship a Docker image, Compose file, or Caddy configuration. The public npm install is also blocked until the Lycato workspace packages are published together.
Scope
This runbook covers one company repo on one trusted host:
- the built cockpit UI and API;
- periodic adapter sync;
- the minute-based agent scheduler;
- local backups and upgrades.
The cockpit has no application authentication. Keep it on loopback and use an authenticated reverse proxy, SSH tunnel, Tailscale, or another private-network control if it must be reached remotely.
Prerequisites
- Node.js 22;
- pnpm via Corepack;
- Git;
- a Unix-like host for unattended operation.
First boot from source
Build Lycato once, outside the company repo:
git clone https://github.com/axysar/lycato.git /opt/lycato
cd /opt/lycato
corepack enable
pnpm install --frozen-lockfile
pnpm build
Create a company repo in a separate directory. init is interactive and must
run inside the target directory:
mkdir -p /srv/lycato/company
cd /srv/lycato/company
node /opt/lycato/apps/cli/bin/run.js init
node /opt/lycato/apps/cli/bin/run.js doctor
Start the supervisor from that company repo:
cd /srv/lycato/company
node /opt/lycato/apps/cli/bin/run.js serve --interval 900
The cockpit and API share http://127.0.0.1:3001/. lycato serve:
- starts the built cockpit UI, Fastify API, and WebSocket endpoint;
- runs sync immediately and then at the requested interval;
- checks scheduled agents every minute, replaying elapsed minute slots after timer drift or host suspension;
- appends run evidence to
.founder/runs/YYYY-MM.jsonl.
Use --skip-cockpit, --skip-sync, or --skip-agents when a separate process
owns that responsibility.
daily-digest does not write unattended company truth. Approve or deny its
action digest explicitly:
node /opt/lycato/apps/cli/bin/run.js agent approvals
node /opt/lycato/apps/cli/bin/run.js agent approve <digest>
Running under a service manager
Use /srv/lycato/company as the working directory and this executable:
/usr/bin/node /opt/lycato/apps/cli/bin/run.js serve --interval 900
Configure the service manager to restart on failure and stop it with SIGINT
or SIGTERM. Keep .founder/keys.env mode 0600; Lycato loads it when the
supervisor starts.
Upgrades
Stop the supervisor before upgrading:
cd /opt/lycato
git pull --ff-only
pnpm install --frozen-lockfile
pnpm build
cd /srv/lycato/company
node /opt/lycato/apps/cli/bin/run.js migrate
node /opt/lycato/apps/cli/bin/run.js doctor
Then restart the supervisor. Migrations are forward-only and idempotent.
Backup and restore drill
node /opt/lycato/apps/cli/bin/run.js backup --out /backups/lycato-$(date +%F).lycatobak
node /opt/lycato/apps/cli/bin/run.js restore \
--from /backups/lycato-YYYY-MM-DD.lycatobak \
--to /tmp/lycato-restore-drill
The company directory is also a Git repo. A private remote is a useful second
backup tier, but it does not replace a tested .lycatobak restore.
Operational checks
Run these from /srv/lycato/company:
node /opt/lycato/apps/cli/bin/run.js doctor
node /opt/lycato/apps/cli/bin/run.js logs --kind agent --since 2026-05-01
curl --fail http://127.0.0.1:3001/api/health
/api/health reports the repository root, bind host and port, authentication
mode, and network posture.
Hardening
- Leave
LYCATO_COCKPIT_HOSTunset to retain the loopback-only default. - If a non-loopback bind is unavoidable, put a real authentication boundary in
front and set both
LYCATO_COCKPIT_HOSTandLYCATO_COCKPIT_AUTH_MODE=reverse-proxy. Lycato refuses the bind without that explicit declaration; it does not add the proxy boundary itself. - Have that proxy strip any inbound
X-Lycato-Actorand set it from the authenticated identity. Review receipts are bound to that actor when present, as well as to an HttpOnly same-site browser session, the branch, and the exact Git diff. Approval requires a fresh, single-use receipt from the diff route. - The default CORS allowlist contains
https://lycato.app,https://www.lycato.app, and the local Vite origin. SetLYCATO_CORS_ORIGINto a comma-separated replacement allowlist when using a different frontend. The same-origin built UI is accepted automatically. - Set
cockpit.live_updates: falsein.founder/config.ymlonly when the proxy cannot carry WebSocket upgrades. - Run Lycato as an unprivileged account with write access only to the company repo and backup destination.
Not included
The source-hosted path does not include Docker packaging, TLS automation, passkeys/SSO, Stripe billing, KMS-backed secrets, multi-tenant orchestration, or managed off-host backups. Those are separate hosted-product work, not implied by this runbook.