Hosting reference#
Choose a complete setup guide on the hosting page. This page covers optional configuration and deployment boundaries.
What the installer does#
Everything below is one script, deploy/install.sh.
- Installs bun, build-essential, python3, GitHub CLI, git, Caddy, and Chrome (headless, for page-preview cards); fetches Isomux and builds it.
- Runs Isomux as a systemd service under a dedicated
isomuxuser, restarting on failure and on boot. - Makes new files readable only by the
isomuxaccount. To share them, for example with a web server, setUMask=0022withsudo systemctl edit isomux, restart, and keep the files outside/home/isomux. - Sets up the
isomuxaccount so apps agents build keep running without anyone logged in and start again after a reboot. - Serves your domain through Caddy with an automatic Let's Encrypt certificate. Caddy keeps a size-bounded request log for 14 days and redacts invite and app sign-in credentials from URLs. Its admin API is turned off, since anything on the box could otherwise reconfigure the proxy without a credential - so apply Caddyfile edits with
systemctl restart caddy, notreload. - Hardens the box: firewall allowing only web traffic and, unless disabled, SSH; key-only SSH auth in the general SSH settings (a
Matchrule for one user or address keeps its own setting); unattended security updates (a standard Ubuntu feature - it patches system packages, never Isomux itself). - Checks that the
isomuxaccount cannot log in as root, and stops the install if the account can. See root access. - Sets up out-of-memory protection and caps the office's memory below the box's RAM, leaving room for SSH. Boxes under 4 GB are not capped. See running out of memory.
- Makes the sandbox that Codex agents run their tools in actually work. On Ubuntu 24.04 that takes one small AppArmor policy file which the sandbox's own package doesn't ship. The installer tries the sandbox first and only acts if it is broken, so a box where it already works is left alone. If the installer still can't get the sandbox working, the install carries on and says so in the output.
- Claims the office owner with the server's setup key before the box is exposed, then mints your invite link.
Agent process isolation#
On Linux, each agent session runs in its own process namespace, so an agent sees only its own processes and cannot read the environment of other agents. A member's OpenCode agents share one namespace. Inside it, sudo, setuid programs and nested sandboxes (such as Chrome's own sandbox or rootless podman) do not work. To turn it off, an office owner clears Isolate agent processes in Office Settings. It is off on macOS, and where the host does not let bubblewrap make a namespace, such as Docker's default security profile. On Ubuntu with bubblewrap's AppArmor profile, that profile would block network access for browsers such as Chrome. The installer adds AppArmor rules that keep those browsers out of it. On your own hardware, run sudo bash deploy/bwrap-apparmor.sh once from your checkout; until then, it is off. This is not a security boundary: an agent can still act as another agent. See the security audit.
Root access#
Agents run as the isomux account, so anything that account can do, an agent can do. If that account can log in as root, it can turn off every guardrail Isomux puts in front of it.
The installer checks this itself. It tries to log in as that account at every address SSH answers on, and asks what the account may do with sudo. It checks when the account is created and again before it prints the invite link. If it gets in, or cannot tell, the install stops. The check cannot be skipped; fix the box instead.
What the check promises when it passes: the Isomux service account cannot log in as root over SSH on this box, and cannot sudo. There is one deliberate exception - that account can ask root to apply an Isomux release, which is what the update button in the office header runs. It can start nothing else.
The usual cause of a failure is a key file kept on the server that root accepts. The key from your own computer is fine - that file stays on your computer. A key made on the box, for GitHub or a deploy script, is not: anything running there can read it and log in as root with it. When the check fails it names the file and tells you which line to remove.
After fixing it:
sudo isomux-harden-ssh
That command applies the SSH hardening and re-runs the check. A pass is a snapshot of the moment it runs - giving root a new key later reopens the hole - so run the command again whenever root's key list changes.
Each Isomux update re-runs a read-only check of the firewall and the SSH boundary on an installer-managed VPS, and prints a warning when either no longer holds. The same check, on demand, is sudo isomux-verify-hardening --check; it changes nothing.
Parameters#
Environment variables for the default direct-host installation, set before running:
| Variable | Default | Meaning |
|---|---|---|
ISOMUX_INSTALL_MODE |
host |
Set to container for the AWS container installation. |
DOMAIN |
(required) | Public domain for the office. |
ISOMUX_REF |
latest release | Git branch, tag, or commit to install. |
ISOMUX_REPO |
GitHub | Git repo to install from (for forks). |
SSH_PORT |
22 |
SSH port to allow through the firewall; none keeps SSH closed. |
DRY_RUN |
(unset) | Set to 1 to print what would run instead of running it. |
App hostnames#
Each app an agent registers can get its own address, like hello.office.example.com, open from any device and behind the same sign-in as the office. A fresh install sets up the proxy side, and the wildcard A record (VPS guide) points the names at the server. An office installed before this existed gets the proxy side from one re-run of the installer, or from adding the site block to /etc/caddy/Caddyfile by hand; an update replaces only a byte-exact older installer rendering, and only to add its access log.
The Apps page shows a short link such as office.example.com/hello, which opens the app with a chat widget.
Certificates are obtained per app the first time it is opened. Two things follow:
- The office answers 404 for every name under its domain that is not a live app, so a subdomain you pointed at this server for something else stops working after updating.
- Deleting an app stops new certificates immediately, but TLS may keep terminating from Caddy's warm cache until its next cold load.
A tailnet office (*.ts.net) keeps port links: Tailscale has no wildcard names, so app hostnames can't resolve there.
How much memory an office needs#
| Engine | Idle agent | Busy agent |
|---|---|---|
| Claude Code | 195-253 MB | 314-421 MB |
| Codex | about 120 MB | 136-158 MB |
Add about 260 MB for the office server and about 1 GB for the operating system.
What the agents run costs more than the agents. A large build can take 3 GB (an Isomux build did), and tests and browsers add up the same way. Several builds at once is the usual way an office runs out of memory: an 8 GB box ran 10 or more agents most of the time, but not several builds at once.
Running out of memory#
A busy office can use up the box's memory. Left to the kernel that ends badly: the machine swaps until nothing responds, SSH included, and on a cloud box only a reboot from the provider's console brings it back.
Isomux handles its half automatically: the office marks every process it starts as a better out-of-memory kill candidate than the office server itself, so a spike usually costs one runaway agent or build instead of the whole office. Message a killed agent to bring it back. Linux only, no setup, no privileges.
The box-wide half is earlyoom, which kills one process while there is still memory left to act with. The order is deliberate: the processes the office starts go first, largest first, then the office server and Caddy, and last of all what keeps the box reachable and usable at all - SSH, Tailscale, DNS, networking. Anything it kills from that last group is set to keep retrying rather than give up, so a burst of kills can't leave DNS or the office switched off for good. The same setup also gives the box a swap file of up to 8 GB if the box has none, smaller only if the disk cannot hold that, and tells the kernel to prefer dropping caches over swapping. On a box with 4 GB of RAM or more, it caps the office's memory at 1 GB under the RAM, leaving room for SSH; the cap reaches a running office without a restart, whether it runs as a system or a user service. A small root timer re-applies the office's own kill-order stamp within a minute of any office restart - a user-level service cannot hold that setting itself.
The VPS install sets all of this up and leaves the tool on the box:
sudo isomux-oom-protect --dry-run # print what would change
sudo isomux-oom-protect
On your own hardware, the same script runs from your checkout as sudo bash deploy/oom-protect.sh, and installs itself at /usr/local/sbin/isomux-oom-protect. Either way the run is safe on a live office: nothing but earlyoom is restarted, and swap the box already has is left exactly as it is, even when it is smaller than a fresh install would get - replacing live swap means taking it offline first, which is not something to do to a running box on your behalf. The run prints the commands if you want to do it yourself.
macOS and Windows get neither half - both mechanisms are Linux-specific.
Locking the office down#
When the office is down or under attack, the rescue command on the office's machine checks it, locks it, rolls it back and writes a report for support. It needs no running office and no model. On an install made by the installer (a VPS or an AWS container), run it as root: sudo isomux-rescue <command>. Otherwise run it as the user that runs Isomux, in the checkout: bun run rescue <command>.
statusshows the service, the ready check, the lock, the version, free disk, the last backup and the last log lines. It exits 0 when the office serves, 1 when it is down and 2 when it is locked.lockdown --reason "<why>"locks the office. It revokes every sign-in, API token, invite and browser extension pairing, ends every agent, and stops schedules, webhooks, apps and messaging bridges. The office then shows only a locked page; updates still install. Add--stopto keep the office stopped instead, also after a reboot.unlockremoves the lock and starts the office. Owners then sign in again with the owner sign-in recovery.restartrestarts the office and waits until it answers.export [folder]writes a readable copy of the office's content to a new folder: every conversation, task, ask, memory and file. It removes secret values and leaves out credentials. Without a folder, it picks one and prints it. The office can be up, down or locked. On a container install, the container must run: start a stopped one first (a locked office starts locked).rollback [release]goes back to the release the last update replaced, or to the release you name, through the updater. It prints the command that goes forward again. A locked office stays locked: the updater refuses a release without locked mode, and does not start an office thatlockdown --stopstopped. On a checkout,bun run rescue rollbackprints the steps instead.report [file] [--note "<what you tried>"]writes an incident report for Isomux support: the status, the last update, the lock history and the last log lines, with secret values removed. Read it before you send it.
A lockdown keeps provider keys and connection tokens, but nothing uses them while locked. If they may have leaked, rotate them at the provider. A lock and an unlock each add a row to the audit log.
From inside the office, an owner or an agent whose manager is an owner can lock the office and export its content with the /isomux-rescue skill. Only an owner signed in to the office in a browser can download the export, and the file is deleted an hour after it is ready. A lock signs everyone out at once, and the office stops. If its service manager starts it again, it starts locked. Code that runs as the office's user can remove a lock made from inside the office, on every install: for a lock it cannot remove, run lockdown on the machine.
Run the command from a login shell on the machine, not from an office terminal or agent shell: stopping the office would end it. The lock holds against members, tokens and agents. On an install that is not run as root, code that already runs as the office's user can remove the lock. Apps start again at boot before the office stops them, so a locked office's apps can run for a few seconds after a reboot.
A backup taken before the lock still holds the revoked sign-ins: restoring it brings them back.
Enterprise Contract#
A company of more than 10 people that uses Isomux commercially needs an enterprise account at cloud.isomux.com/enterprise. In each of its offices, an owner checks "This office is part of an Enterprise Contract" in Settings -> Office -> Enterprise and pastes the secret from the account page. Until a secret is accepted, agents take no messages and the office adds no members.
The office then asks the account for a seat before it adds a member and frees the seat when it removes one. It sends only a random office id and its member count (security audit, section 8.12). When the account has no free seat, the office refuses the add and says so; IT adds seats on the account page. Nobody is ever locked out once a secret is accepted. An account can go over its seats in two ways: an office adds members while it cannot reach Isomux (it adds them anyway and sends the count at its next hourly check-in), or a secret is pasted in an office that has more members than the account has free seats. IT sees the new count on the account page. When IT makes a new secret, paste it in each office: until then each office refuses new members. Unchecking the box frees the office's seats on the account.
Network bind#
~/.isomux/office-config.json can set networkBind to "loopback", "all", or "auto". "auto" binds loopback before the office has an owner or while external access is off, and all interfaces otherwise. Remove the field to use the same runtime default while allowing the installer or updater to select "loopback" when it verifies a local proxy. An explicit "auto" opts out of that automatic installer change. The loopback listener uses IPv4 127.0.0.1; callers that use localhost fall back to it on dual-stack hosts.
What each deployment covers#
Two facts set the boundary: whether a proxy sits in front of Isomux, and whether the office has a real domain. Only a real domain gives apps their own web addresses.
Every shape supports Claude on Amazon Bedrock. The operator supplies AWS credentials, region, and model access; the installer does not configure AWS.
Hosted Isomux in the first two rows is the paid managed service, where we run the server for you.
Proxy and real domain#
| Shape | Reach the office | App addresses | Firewall | Request log | Isomux does not cover |
|---|---|---|---|---|---|
| New Hosted Isomux office | Its HTTPS domain | One hostname per app | The installer configures it. Updates verify it and report a warning without changing it. | Caddy records client address, host, redacted path, status, and time for 14 days. | Provider controls and traffic that bypasses Caddy. |
| Existing Hosted Isomux office | Its HTTPS domain | One hostname per app | The installer owns it. Updates verify it and report a warning without changing it. | The first update adds the same 14-day Caddy log when the front-door config still matches the installer exactly. | Provider controls, traffic that bypasses Caddy, and request history before logging starts. |
| Self-hosted VPS installed by Isomux | Its HTTPS domain | One hostname per app | The operator owns it. The installer configures it, and updates verify it without changing it. | A current install records the same 14-day Caddy log. An update adds it to an exact older installer rendering; an edited file is left alone. | Provider controls, operator changes, and traffic that bypasses Caddy. |
| Hand-provisioned VPS | Its HTTPS domain | One hostname per app when the operator configured the wildcard proxy | The operator owns and verifies it. Isomux updates do not assume the installer configured it. | Only what the operator configured. An update changes only a byte-exact installer Caddyfile. | Firewall setup, proxy maintenance, retention, and traffic that bypasses the proxy. |
When an active Caddy config forwards to 127.0.0.1:4000, an install or update records that fact in the office config. After the install or update restarts Isomux, the direct :4000 address stops answering; the Caddy address keeps working. This also applies to a hand-provisioned VPS. Set networkBind to "all" in ~/.isomux/office-config.json before the update to keep the direct port.
The same step sets trustedProxy to "same-host", so Isomux rate-limits each client by the address Caddy sends in X-Forwarded-For. Behind your own proxy on the same machine, set "trustedProxy": "same-host" in ~/.isomux/office-config.json and restart Isomux. The proxy must send X-Forwarded-For. The container images set "load-balancer" themselves. On Render it is unchecked whether more than one proxy is in front; if there is, the limits apply per Render proxy, not per client.
Agent, cron-run and app tokens work only from the machine itself. Isomux refuses them on a request that came through a proxy, which it recognizes by an X-Forwarded-For, Forwarded or X-Real-IP header. A proxy or tunnel that sends none of these looks like the machine itself.
Containers#
The container setups also have a proxy and a real domain.
| Shape | Reach the office | App addresses | Firewall | Request log | Isomux does not cover |
|---|---|---|---|---|---|
| AWS EC2 container | Its HTTPS domain | One hostname per app | The installer configures it. Container updates do not verify it. | Caddy keeps the same 14-day log as a VPS install. | AWS controls such as the security group, and traffic that bypasses Caddy. |
| Render | Its HTTPS domain | One hostname per app | Isomux does not configure one. | No Isomux access log. Render's service logs show office output. | Render's platform controls, its service logs, and copies of the disk. |
| Kubernetes (EKS) | Its HTTPS domain, through the load balancer | One hostname per app | The cluster operator owns it. The manifests add an egress policy that blocks the instance metadata address. | No Isomux access log. The manifests do not turn on load balancer access logs. | The cluster, node settings, the load balancer, and the log pipeline. |
Proxy and no real domain#
| Shape | Reach the office | App addresses | Firewall | Request log | Isomux does not cover |
|---|---|---|---|---|---|
| Home box with Tailscale Serve or Funnel | Its *.ts.net HTTPS address |
No separate hostnames; use each app's port | The operator owns it. The installer does not configure or verify it. | No Isomux Caddy access log. Tailscale controls any proxy log. | Tailscale policy, firewall policy, proxy logs, and direct app ports. |
The Isomux installer and updater do not manage this shape, so updates do not change its network bind.
No proxy and no real domain#
| Shape | Reach the office | App addresses | Firewall | Request log | Isomux does not cover |
|---|---|---|---|---|---|
| Home box on a tailnet | http://name:4000 |
No separate hostnames; use each app's port | The operator owns it. The installer does not configure or verify it. | No front-door access log. | Tailnet access, firewall policy, request logs, and app ports. |
| One local machine | http://localhost:4000 |
No separate hostnames; use each app's port | The machine owner controls it. | No front-door access log. | Other local processes and any exposure the operator adds. |
These shapes do not run the system installer or its service-account updater. They get neither its firewall verification nor its Caddy access log, and updates do not change their network bind.