Team Deployment¶
Run a self-hosted, production team-mode stack on a single VM: Caddy (automatic HTTPS) → omniagent → PostgreSQL, via Docker Compose. This is the single-host model — no Kubernetes, no multi-region. For the config reference and local-trial setup, see the Team Mode guide; for deploying a single-operator (personal-mode) container via OmniDeploy instead, see Deployment.
All the compose assets referenced below live in
deploy/team/prod/
in the repository.
Provisioning a Lightsail instance¶
- Create an AWS Lightsail VM instance (not a container service) — the smallest plan with 2GB+ RAM is enough for a small team. Choose Ubuntu.
- Attach a static IP and open ports 80/443 in the instance firewall (Caddy needs both for HTTP→HTTPS redirect and ACME HTTP-01 validation).
- Point your domain's DNS
Arecord at the static IP. - SSH in and install Docker + the Compose plugin:
Deploying the stack¶
git clone https://github.com/plexusone/omniagent.git
cd omniagent/deploy/team/prod
cp .env.example .env
$EDITOR .env # fill in POSTGRES_PASSWORD, APP_DB_PASSWORD, DOMAIN,
# SUPERADMIN_EMAIL, SMTP_*, and an LLM provider key
docker compose up -d --build
This builds the omniagent image from the repository root, starts
PostgreSQL (not published to the host — only Caddy exposes 80/443), and
Caddy requests a Let's Encrypt certificate for DOMAIN automatically. Named
volumes (pgdata, caddy_data, caddy_config) persist database contents
and the certificate cache across docker compose down/restart.
Smoke checklist¶
Run through this after first deploy (and after any upgrade):
- Health check —
curl -s https://$DOMAIN/api/healthreturns200. - Magic-link login — request a link for
SUPERADMIN_EMAILathttps://$DOMAIN/. If SMTP is configured, check your inbox; if not,docker compose logs omniagent | grep verifyand open the logged/api/auth/verify?token=…URL manually (seedeploy/team/dev/TESTING.mdfor the same flow against the local dev stack). - Superadmin confirmed — after verifying, the web UI shows the Admin
tab and
/api/auth/mereports"superadmin":true. - Allowlist a second member — from the Admin tab, add a teammate's email, then have them sign in and confirm they land in chat with no Admin tab.
- Agent reply — if an LLM key is set, message the agent in a private chat and confirm a reply arrives.
- SSO sign-in (if
GOOGLE_CLIENT_ID/GITHUB_CLIENT_IDare set) — click the Google/GitHub button on the login screen, complete the provider's consent screen, and confirm you land back in the app signed in. Sign in again with the same provider account using a second browser/incognito window and confirm no duplicate account is created. If an existing magic-link user signs in via SSO with the same email, confirm the Admin tab's Members card shows bothmagic_linkand the SSO provider as badges on one row (not two separate users).
Operations¶
Upgrading¶
Schema migrations run automatically and idempotently on every omniagent
start (advisory-locked, so it's safe even if a deploy briefly overlaps
itself) — no separate migration step is needed. Postgres and Caddy are
unaffected by an omniagent-only rebuild.
Rolling back¶
Check out the previous commit/tag and rebuild:
Rolling back across a schema migration that isn't backward-compatible is a manual judgment call — check the target version's changelog for breaking migrations before rolling back a database that's already been migrated forward.
Environment variable matrix¶
Every team.* config field has an OMNIAGENT_TEAM_* environment variable
equivalent, used by the compose file's environment: block — see the full
table in Environment Variables → Team Mode.
Backups¶
./backup.sh # writes deploy/team/prod/backups/omniagent-team-<timestamp>.sql.gz
./restore.sh backups/<file> --force # OVERWRITES the current database
Run these from deploy/team/prod/ with the stack up. Automate backup.sh
on a cron schedule and ship the output off-host. To verify a backup restores
cleanly without touching production, restore it into a scratch database:
docker compose exec postgres psql -U omniagent_owner -c \
"CREATE DATABASE restore_check OWNER omniagent_owner;"
gunzip -c backups/<file> | docker compose exec -T postgres psql -U omniagent_owner -d restore_check
docker compose exec postgres psql -U omniagent_owner -d restore_check -c "SELECT count(*) FROM users;"
docker compose exec postgres psql -U omniagent_owner -d omniagent_team -c "DROP DATABASE restore_check;"
The same procedure runs automatically (against a disposable CI database, not
production) via the
team-backup-restore
GitHub Actions workflow — trigger it manually with workflow_dispatch to
verify the restore path still works after a schema change.
Troubleshooting¶
| Symptom | Likely cause |
|---|---|
omniagent container exits immediately |
Bad or missing POSTGRES_PASSWORD/APP_DB_PASSWORD/SUPERADMIN_EMAIL/DOMAIN — these are required (:?-guarded) in the compose file's environment: block; check docker compose logs omniagent. |
| Magic links never arrive | SMTP misconfigured. Team mode does not fail startup on bad SMTP — it falls back to logging the link instead of emailing it. Check docker compose logs omniagent for the link, then fix SMTP_* in .env. |
| Caddy never gets a certificate | Port 80/443 not reachable from the internet (firewall/security group), or DNS hasn't propagated yet to the instance's IP. docker compose logs caddy shows the ACME error. |
docker compose up fails on the postgres service |
APP_DB_PASSWORD unset when the database first initialized — the app role is only created via init/01-app-role.sh on a fresh data volume. If you changed APP_DB_PASSWORD after first boot, update the role's password directly (ALTER ROLE omniagent_app PASSWORD '...') rather than expecting the init script to re-run. |
omniagent container exits immediately with Google SSO configured |
OIDC discovery against accounts.google.com failed at startup (no outbound internet, DNS/firewall block). This is fatal by design — check docker compose logs omniagent for the discovery error; GitHub SSO makes no such call and is unaffected. |
SSO redirects back with ?error=sso_state or ?error=sso_failed |
The provider's registered redirect URI doesn't exactly match https://$DOMAIN/api/auth/{google,github}/callback, or the client ID/secret is wrong — re-check the provider's app settings against .env. |
SSO redirects back with ?error=not_allowed |
The signed-in account's verified email isn't on the allowlist — add it from the Admin tab, then retry. |