Skip to content

Standing up and maintaining a box (operator)

For whoever runs the box. This is the most technical of the docs.

A Lowkey box is one self-contained deployment for one person or one trusted group: its own server, data, projects, provider logins, and agents. The person who runs that server is the operator.

The provisioner is for a fresh Ubuntu VPS. It gets the machine ready to run Lowkey in the product shape: an operator user, app files under /opt/lowkey, nginx in front, TLS for your domain, and system-level hardening around SSH, firewalling, memory pressure, and provider tooling.

Point the domain’s DNS at the VPS first, then run the provisioner from a Lowkey checkout:

Terminal window
./provision/provision.py joy-lowkey 203.0.113.10 --user joy --domain joy.example.com

The first argument becomes the system hostname. The IP address is where the script connects as root. --user is the operator account it creates and uses for normal Lowkey work. --domain tells it to render the product nginx template and request a Let’s Encrypt certificate with certbot.

You can omit --domain if DNS is not ready yet. In that case nginx is installed, but the Lowkey site and TLS certificate are left for later.

The script is designed to be re-run. It creates or updates the same pieces each time: packages, Docker, Node, Python, Go, provider CLIs, the lowkey CLI shim, the operator user, /opt/lowkey, swap, systemd daemon drop-ins, SSH hardening, UFW, fail2ban, nginx, and, when a domain is provided, the nginx site and TLS certificate. Re-running is normal when a previous pass stopped halfway, but remember that it reapplies the intended firewall and nginx choices.

After provisioning, the box has the operating-system side ready. The app checkout is expected to live in /opt/lowkey, owned by the operator user. Keep that path outside any workspace root; otherwise Lowkey can discover its own application checkout as a project.

The daemon is expected to run as lowkey-daemon.service, with memory and per-session containment drop-ins already staged under /etc/systemd/system/lowkey-daemon.service.d/. The web app runs separately as the web service in Docker Compose. The nginx template sends API and websocket traffic to the daemon on 127.0.0.1:7070, sends the web UI to 127.0.0.1:3000, redirects HTTP to HTTPS, and uses the daemon’s /api/auth endpoint for cookie auth.

Provisioning does not yet finish the whole box by itself. The remaining manual work is to put the Lowkey code into /opt/lowkey, deploy it there, generate ~/.lowkey/config.json with a fresh password and API key, choose workspace roots that do not include /opt/lowkey, then connect providers in Settings -> Providers.

Treat a box as a trust boundary. Anyone with access to it can ask agents to act with that box’s files, provider sessions, and local credentials. Add people only when you would trust them with the whole machine.

Provisioning also changes real infrastructure: SSH policy, firewall rules, nginx, TLS, and systemd configuration. Re-run it deliberately, and schedule app deploys or daemon restarts for a time when interrupting active work is okay.

  • Connecting providers -> /operator/providers/
  • Defining agents -> /operator/agents-config/
  • Sharing a box with people -> ../multi-user.md