Most setups need one Gateway - a single Gateway handles multiple messaging connections and agents. Run separate Gateways with isolated profiles/ports only when you need stronger isolation or redundancy (e.g., a rescue bot).
Rescue-bot quickstart
The simplest rescue-bot setup:
- Keep the main bot on the default profile.
- Run the rescue bot on
--profile rescue, with its own Telegram bot token. - Put the rescue bot on a different base port, e.g.
19789.
This keeps the rescue bot able to debug or apply config changes if the primary bot is down. Leave at least 120 ports between base ports so derived sandbox/browser/CDP ports never collide. Each instance reaches base + 110: its browser control port is base + 2, and that port's CDP range runs to base + 110.
# Rescue bot (separate Telegram bot, separate profile, port 19789)
apus --profile rescue onboard
apus --profile rescue gateway install --port 19789
If your main bot is already running, that's usually all you need. If onboarding already installed the rescue service, skip the final gateway install.
During apus --profile rescue onboard:
- Use a separate Telegram bot token, dedicated to the rescue account. It is easy to keep operator-only, it stays independent from the main bot's channel and app install, and it gives a simple DM-based recovery path.
- Keep the
rescueprofile name. - Use a base port at least 120 higher than the main bot.
- Accept the default rescue workspace unless you already manage one yourself.
What --profile rescue onboard changes
--profile rescue onboard runs the normal onboarding flow but writes everything into a separate profile, so the rescue bot gets its own:
- Profile/config file
- State directory
- Workspace (default:
~/.apus/workspace-rescue) - Managed service name
- Base port (plus derived ports)
- Telegram bot token
Prompts are otherwise identical to normal onboarding.
General multi-gateway setup
The same isolation pattern works for any pair or group of Gateways on one host. Give each extra Gateway its own named profile and base port.
apus setup runs onboarding on a profile that is not configured yet. It does the same first-run job as the onboard command used for the rescue bot above. Use onboard when you want the onboarding flow on a profile that is already configured.
# main (default profile)
apus setup
apus gateway --port 18789
# extra gateway
apus --profile ops setup
apus --profile ops gateway --port 19789
Named profiles on both sides also work:
apus --profile main setup
apus --profile main gateway --port 18789
apus --profile ops setup
apus --profile ops gateway --port 19789
Services follow the same pattern:
apus gateway install
apus --profile ops gateway install --port 19789
Use the rescue-bot quickstart for a fallback operator lane. Use the general profile pattern for multiple long-lived Gateways across different channels, tenants, workspaces, or operational roles.
Isolation checklist
Keep these unique per Gateway instance:
| Setting | Purpose |
|---|---|
APUS_CONFIG_PATH |
Per-instance config file |
APUS_STATE_DIR |
Per-instance sessions, creds, caches |
agents.defaults.workspace |
Per-instance workspace root |
gateway.port (or --port) |
Unique per instance |
| Derived sandbox/browser/CDP ports | See below |
Sharing any of these causes config, state, or port conflicts. Gateway startup
enforces unique state-directory ownership, including when
APUS_ALLOW_MULTI_GATEWAY=1 is set.
APUS_STATE_DIR alone does not isolate a managed Gateway service. Service names follow the profile, not the state directory. For onboarding or service-install tests, use a dedicated named profile and unique ports, or an isolated machine. Do not install or restart the default service against a temporary state directory.
Port mapping (derived)
Base port = gateway.port (or APUS_GATEWAY_PORT / --port).
- Sandbox host listener port = base + 1 when the isolated widget/MCP App host is active (unless
mcp.apps.sandboxPortoverrides it). - Browser control service port = base + 2 (loopback only).
- Hosted widget documents and A2UI renderer assets are served on the Gateway HTTP server itself (same port as
gateway.port). - Browser profile CDP ports auto-allocate from
browser control port + 9through+ 108.
Override any of these in config or env and you must keep them unique per instance.
Browser/CDP notes (common footgun)
- Do not pin
browser.cdpUrlto the same value on multiple instances. - Each instance needs its own browser control port and CDP range (derived from its gateway port).
- For explicit CDP ports, set
browser.profiles.<name>.cdpPortper instance. - For remote Chrome, use
browser.profiles.<name>.cdpUrl(per profile, per instance).
Manual env example
APUS_CONFIG_PATH=~/.apus/main.json \
APUS_STATE_DIR=~/.apus \
apus gateway --port 18789
APUS_CONFIG_PATH=~/.apus/rescue.json \
APUS_STATE_DIR=~/.apus-rescue \
apus gateway --port 19789
Quick checks
apus gateway status --deep
apus --profile rescue gateway status --deep
apus --profile rescue gateway probe
apus status
apus --profile rescue status
apus --profile rescue browser status
gateway status --deepcatches stale launchd/systemd/schtasks services from older installs.doctor --deepalso reports when a recognizable service definition or native service manager could not be inspected. Restore inspection access and rerun Doctor; an incomplete scan does not prove that no other service exists. Inspection warnings never authorize service cleanup.gateway probewarning text such asmultiple reachable gateway identities detectedis expected in two cases. You intentionally run more than one isolated gateway, or Apus cannot prove that reachable check targets are the same gateway. An SSH tunnel, proxy URL, or configured remote URL to the same gateway is one gateway with multiple transports, even when transport ports differ.