---
title: "Self-host Omnara"
description: "Run Omnara, the open-source managed agent platform, on one exe.dev VM: its whole Docker Compose stack, models from exe.dev's LLM gateway, no API keys."
url: https://exe-staging.dev/deploy/omnara
category: "AI agents"
updated: 2026-10-31
---

# Self-host Omnara

Your own managed agent platform on one private VM. No model API keys.

- **Software:** Omnara (https://omnara.com)
- **Source:** https://github.com/omnara-ai/omnara
- **License:** Apache-2.0
- **Time:** 15 minutes
- **Example VM name:** `my-omnara` (yours will differ; on the VM, `hostname` prints it)
- **Machine-readable:** https://exe-staging.dev/deploy/omnara/manifest.json
- **File:** https://exe-staging.dev/deploy/omnara/backup.sh
- **File:** https://exe-staging.dev/deploy/omnara/compose.exe.yaml
- **File:** https://exe-staging.dev/deploy/omnara/exe-mail.py

## What you get

- Omnara 0.2.15's whole Compose stack at your VM's private URL, started at boot by omnara.service
- A relay to exe.dev's LLM gateway that lists its models, so agents need no model API key on the VM
- Signup, password reset, and invitation emails sent through exe.dev, with no mail account
- A user, omnara-agent, ready to be your agents' machine, without sudo and cut off from the stack's databases
- Omnara's encryption keys in ~/omnara/.env, mode 600
- Nightly backups of the database, artifacts, and memory, with the first one taken and checked

Running on the VM:

- **Omnara 0.2.15**: API, worker, maintenance, web app, and nginx gateway, from Omnara's images in Docker Compose, port 8000
- **PostgreSQL, Valkey, RustFS**: Agent event logs, queues, and artifacts, on the Compose network only, data in `Docker volumes omnara_postgres18-data, omnara_rustfs-data, omnara_memory-data`
- **LLM relay**: nginx next to the API and worker, forwarding to exe.dev's LLM gateway
- **Mail bridge**: exe-mail.py next to the API, sending Omnara's email through exe.dev
- **omnarad**: Omnara's machine daemon, running agents' commands as the user omnara-agent, data in `/home/omnara-agent`

How it is reached:

- https://my-omnara.exe.xyz/ (private): Omnara's dashboard and API, behind exe.dev login, then Omnara's own
- https://my-omnara.exe.xyz/ (shared): Teammates you share the VM with, each with their own Omnara account

## Deploy with Shelley

Open https://exe-staging.dev/new?prompt=Set%20up%20Omnara%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Fomnara.md%20from%20top%20to%20bottom.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-omnara.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-omnara.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Omnara%200.2.15%27s%20whole%20Compose%20stack%20at%20your%20VM%27s%20private%20URL%2C%20started%20at%20boot%20by%20omnara.service%0A-%20A%20relay%20to%20exe.dev%27s%20LLM%20gateway%20that%20lists%20its%20models%2C%20so%20agents%20need%20no%20model%20API%20key%20on%20the%20VM%0A-%20Signup%2C%20password%20reset%2C%20and%20invitation%20emails%20sent%20through%20exe.dev%2C%20with%20no%20mail%20account%0A-%20A%20user%2C%20omnara-agent%2C%20ready%20to%20be%20your%20agents%27%20machine%2C%20without%20sudo%20and%20cut%20off%20from%20the%20stack%27s%20databases%0A-%20Omnara%27s%20encryption%20keys%20in%20~%2Fomnara%2F.env%2C%20mode%20600%0A-%20Nightly%20backups%20of%20the%20database%2C%20artifacts%2C%20and%20memory%2C%20with%20the%20first%20one%20taken%20and%20checked%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details.%20Follow%20%22Install%20by%0Ahand%22%20from%20%22On%20the%20VM%22%2C%20skipping%20VM%20creation%20and%20SSH%3A%20Docker%2C%20Omnara%2C%0A.env%2C%20the%20stack%2C%20omnara.service%2C%20the%20omnara-agent%20user%2C%20and%20backups.%0AWork%20as%20exedev%20in%20~%2Fomnara.%20Never%20overwrite%20an%20existing%20.env%2C%20and%20never%0Aprint%20its%20contents.%20Don%27t%20sign%20up%2C%20create%20organizations%2C%20or%20connect%20a%0Amachine%3A%20the%20owner%20does%20those%20in%20the%20browser%20with%20their%20own%20password%2C%0Aand%20the%20machine%20token%20comes%20from%20their%20dashboard.%20Don%27t%20send%20test%0Aemails.%20Don%27t%20share%20the%20VM%20or%20make%20it%20public.%20Verify%20that%20%2Fhealthz%20says%0Aok%2C%20that%20the%20relay%20lists%20models%2C%20and%20that%20api-mail%27s%20log%20says%20it%20is%0Alistening%2C%20then%20point%20the%20owner%20to%20%22Sign%20up%22.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open%20and%20these%20commands%2C%20which%20only%20I%20can%20run%2C%20from%20my%20own%20computer%20%28with%20this%20VM%27s%20name%20in%20them%29%3A%0A%20%20ssh%20exe.dev%20share%20add%20my-omnara%20teammate%40example.com%0A to create a VM whose agent, Shelley, follows this recipe. From a terminal:

```sh
curl -fsSL https://exe-staging.dev/deploy/omnara/prompt.txt | ssh exe.dev new --prompt=/dev/stdin
```

Then, on your own computer:

```sh
ssh exe.dev share add my-omnara teammate@example.com
```

Or follow the steps below by hand.

[Omnara](https://github.com/omnara-ai/omnara) is an open-source managed
agent platform, a self-hostable alternative to Claude Managed Agents and
OpenAI's Agents API. You define agents in YAML (instructions, model, tools,
MCP servers, skills, memory) and talk to them from its dashboard, its API
and TypeScript SDK, Slack, Discord, GitHub, or a schedule. Every turn is
kept in a PostgreSQL event log, so agents pick up where they left off.
Their commands run on machines you connect with a small daemon, `omnarad`.

This page runs all of it on one exe.dev VM:

- Omnara's own Compose stack, pinned to the release we tested (0.2.15,
  October 30), private behind exe.dev login.
- Models from exe.dev's [LLM gateway](/docs/integrations-llm): GPT, GLM,
  Kimi, Grok, and more. A small relay,
  [compose.exe.yaml](https://exe-staging.dev/deploy/omnara/compose.exe.yaml), sits
  next to Omnara's API and worker, so no model key is stored anywhere.
- The VM itself as your agents' first machine, as a user without sudo.

> [!NOTE]
> Omnara is a young project, released every few days. This page pins
> 0.2.15 and runs it in Omnara's development mode, for the reasons in the
> FAQ. Treat it as a private platform for you and your team, not a public
> service.

## Install

Deploy sets up the stack, the agents' user, and backups on a new VM. To do
it yourself:

<details id="create-the-vm" data-manual-setup class="dp-details">
<summary>Install by hand</summary>

On your computer:

```bash
ssh exe.dev new --name=my-omnara
ssh my-omnara.exe.xyz
```

### Get Omnara

On the VM, start Docker, fetch Omnara at the tested release, and add this
page's Compose additions and backup script:

```bash
sudo systemctl enable --now docker.service
git clone --branch cluster-v0.2.15 --depth 1 \
  https://github.com/omnara-ai/omnara ~/omnara
cd ~/omnara
curl -fsSLO https://exe-staging.dev/deploy/omnara/compose.exe.yaml
curl -fsSLO https://exe-staging.dev/deploy/omnara/exe-mail.py
curl -fsSLO https://exe-staging.dev/deploy/omnara/backup.sh
chmod +x backup.sh
```

`compose.exe.yaml` adds the LLM relay and the mail bridge, keeps
PostgreSQL, Valkey, and RustFS off the VM's ports, and makes them start
again after a reboot. [exe-mail.py](https://exe-staging.dev/deploy/omnara/exe-mail.py)
is the mail bridge: a small SMTP server, reachable only from Omnara's API,
that hands each message to exe.dev's [email gateway](/docs/send-email).

### Write the settings

This makes `.env`, mode 600, with new encryption keys, and refuses to
overwrite one that exists. Omnara needs the same keys to read the secrets
it stores, so `backup.sh` saves `.env` with everything else.

```bash
(umask 077; set -o noclobber; cat > ~/omnara/.env <<EOF
COMPOSE_FILE=compose.yaml:compose.exe.yaml
COMPOSE_PROFILES=app
OMNARA_RELEASE=v0.2.15
OMNARA_PUBLIC_URL=https://$(hostname).exe.xyz
OMNARA_ALLOW_INSECURE_DEV_DEFAULTS=1
OMNARA_SECRET_ENCRYPTION_KEYS='{"k1":"$(openssl rand -base64 32)"}'
OMNARA_SECRET_ENCRYPTION_ACTIVE_KEY_ID=k1
OMNARA_SKILL_DOWNLOAD_SIGNING_KEY=$(openssl rand -base64 32)
OMNARA_EMAIL_DRIVER=smtp
OMNARA_EMAIL_FROM=omnara@$(hostname).exe.xyz
OMNARA_SMTP_ADDR=127.0.0.1:2525
OMNARA_SMTP_REQUIRE_TLS=0
EOF
)
```

### Start it

Run the stack at boot with a small unit, which also brings up the relays
after the containers they join:

```bash
sudo tee /etc/systemd/system/omnara.service >/dev/null <<EOF
[Unit]
Description=Omnara (docker compose)
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
User=$USER
WorkingDirectory=$HOME/omnara
ExecStart=/usr/bin/docker compose up -d --remove-orphans

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now omnara.service
cd ~/omnara
until curl -fs http://localhost:8000/healthz; do sleep 3; done; echo
docker compose exec -T worker-llm curl -fsS http://127.0.0.1:8090/v1/models | head -c 200; echo
docker compose logs api-mail
```

The first start pulls about 2 GB of images, in under a minute. Expect
`{"status":"ok"}`, the start of the gateway's model list, and
`exe-mail listening on 127.0.0.1:2525`.

### Make the agents' user

Agents run their commands as `omnara-agent`, which has no sudo and no
Docker. Lingering keeps its daemon running when nobody is logged in, and
the slice setting blocks it from Docker's networks, where the stack's
databases listen with Omnara's default passwords:

```bash
sudo useradd -m -s /bin/bash omnara-agent
sudo loginctl enable-linger omnara-agent
sudo systemctl set-property user-$(id -u omnara-agent).slice \
  IPAddressDeny=172.16.0.0/12
docker network inspect omnara-control-plane -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
```

The subnet should be inside `172.16.0.0/12` (172.16 to 172.31). You
connect the daemon after signing up, in
[Give agents a machine](#give-agents-a-machine).

### Set up nightly backups

`backup.sh` stops Omnara's API, worker, and maintenance for a few seconds,
so the database, artifacts, and memory are copied together, and keeps the
14 newest backups in `~/omnara-backups`.

```bash
sudo tee /etc/systemd/system/omnara-backup.service >/dev/null <<EOF
[Unit]
Description=Back up Omnara to ~/omnara-backups
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
User=$USER
ExecStart=$HOME/omnara/backup.sh
EOF
sudo tee /etc/systemd/system/omnara-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Back up Omnara every night

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now omnara-backup.timer
sudo systemctl start omnara-backup.service
cd ~/omnara
latest=$(ls -1t ~/omnara-backups/omnara-*.tar.gz | head -n 1)
tar -tzf "$latest"
tar -xzOf "$latest" ./omnara.dump |
  docker compose exec -T postgres pg_restore --list | grep -c 'TABLE DATA'
```

Expect `.env`, `omnara.dump`, `volumes.tar`, and the three files you
downloaded, then a
count of tables (78 in 0.2.15). Installation done; continue with
[Sign up](#sign-up).

</details>

## Sign up

Open `https://my-omnara.exe.xyz/`, sign in to exe.dev if asked, and click
**Sign up** with the email of your exe.dev account. A verification email
comes from exe.dev within a minute. Open its link, choose a password (12
characters or more), and create an organization. It comes with a project
called **Default**.

exe.dev only delivers to addresses it knows (see the FAQ). The bridge
writes a refused message to its log, link and all; to find the newest
link, on your computer:

```bash
ssh my-omnara.exe.xyz 'cd omnara && docker compose logs --no-log-prefix api-mail' |
  grep -o 'https://[^ ]*?token=[0-9a-f]*' | tail -n 1
```

## Add the models

In **Models**, click **New provider**:

1. Choose the **OpenAI** preset and name it `exe-llm`.
2. Under **API key**, make a new secret with any value, say `unused`. The
   relay drops it; exe.dev adds the real credentials.
3. Under **Advanced**, set **Base URL** to `http://127.0.0.1:8090/v1`
   and keep the API format **OpenAI Responses**.
4. Click **Add provider**. Omnara lists the gateway's models: pick a few,
   say `gpt-6.1-sol` and `gpt-6-luna`, share them with the **Default**
   project, and add them.

`127.0.0.1:8090` is the relay inside Omnara's containers, not a port on
the VM, so nothing else can reach it.

## Give agents a machine

In **Machines**, click **Connect machine**, name it `omnara-vm`, share it
with the **Default** project, and copy the machine token (it's shown
once). Then on the VM, paste the token when asked:

```bash
read -rsp 'Machine token: ' token; echo
sudo -iu omnara-agent env XDG_RUNTIME_DIR=/run/user/$(id -u omnara-agent) \
  DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u omnara-agent)/bus \
  OMNARA_API_URL=http://localhost:8000/api/v1 OMNARA_MACHINE_TOKEN="$token" \
  sh -c 'curl -fsSL http://localhost:8000/install/omnarad.sh | sh'
unset token
```

Expect `omnarad restarted`, and the machine shows **Online** in the
dashboard. The daemon is a systemd user service of `omnara-agent`, keeps
its token in `~omnara-agent/.omnarad/daemon.json`, and updates itself
from Omnara's stable releases. Check it with
`sudo -iu omnara-agent .omnarad/bin/omnarad status`.

## Make an agent

In **Agents**, click **New agent**, switch to **YAML**, name it, and
paste:

```yaml
version: v1
instruction: |
  You are an engineering assistant. Use your machine to run commands.
model:
  provider_config: exe-llm
  name: gpt-6.1-sol
machine_sources:
  - machine_name: omnara-vm
```

Click **Create & launch agent**, open **Chat**, and ask it to run
`uname -a` and `df -h /`. The
[agent configuration](https://docs.omnara.com/agents/configuration) docs
cover tools, MCP servers, skills, memory, subagents, and schedules.

<figure>
<img src="https://exe-staging.dev/deploy/omnara/agent-chat.png" alt="Omnara's agent chat: asked to run uname -a, whoami and df -h, the agent calls run_command and answers with the VM's kernel, the user omnara-agent, and 17 GB free disk. The side panel shows the model gpt-6.1-sol on exe-llm and the machine omnara-vm online." width="1280" height="720" loading="lazy">
<figcaption>An agent on a test VM, running a command as omnara-agent through the exe-llm provider.</figcaption>
</figure>

## Use the API

With **All projects** selected, open **API Tokens** and create an org
token. On the VM, the API is at `http://localhost:8000/api/v1`, and the
chat's **cURL** and **TypeScript SDK** tabs show ready-made calls:

```bash
curl http://localhost:8000/api/v1/orgs -H "Authorization: Bearer $OMNARA_TOKEN"
```

From elsewhere, `https://my-omnara.exe.xyz/api/v1` is behind exe.dev
login too. Send an exe.dev [VM token](/docs/https-tokens-for-vms) as
well, which the proxy removes before Omnara sees the request:

```bash
ssh exe.dev ssh-key generate-api-key --vm=my-omnara --label=omnara-api
curl https://my-omnara.exe.xyz/api/v1/orgs \
  -H "X-Exedev-Authorization: Bearer $EXE_VM_TOKEN" \
  -H "Authorization: Bearer $OMNARA_TOKEN"
```

## Bring your team

Share the VM, on your computer:

```bash
ssh exe.dev share add my-omnara teammate@example.com
```

They sign up at `https://my-omnara.exe.xyz/` with the email the VM is
shared with, as you did. Then invite them in **Members** (with **All
projects** selected); they get an email and accept when they sign in.

## Compared with the hosted platforms

- [Claude Managed Agents](/deploy/claude-managed-agents): Anthropic runs
  the agent loop, sessions, and memory, on Claude only. That page gives
  each session a VM for its tools. Less to run, but your agents live on
  Anthropic's platform.
- [OpenAI Agents SDK](/deploy/openai-agents-sdk): a library. You write
  the agent in Python and that page gives each sandbox session a VM.
  More code, full control.
- Omnara: the platform itself, on your VM, with any model. You get its
  dashboard, API, triggers, and event log, and you keep it running and
  updated.

## Back up and restore

Backups run nightly; `~/omnara/backup.sh` takes one now and prints its
path. Each holds the database, the artifact and memory volumes, and
`.env`. Copy them off the VM now and then, on your computer:

```bash
scp 'my-omnara.exe.xyz:omnara-backups/*.tar.gz' .
```

<details id="restore" class="dp-details">
<summary>Restore a backup</summary>

On the VM, this replaces Omnara's data with the newest backup's:

```bash
cd ~/omnara
backup=$(ls -1t ~/omnara-backups/omnara-*.tar.gz | head -n 1)
mkdir /tmp/omnara-restore && tar -xzf "$backup" -C /tmp/omnara-restore
cmp -s .env /tmp/omnara-restore/.env || echo "Keys differ: see below"
docker compose stop api-llm worker-llm api-mail api worker maintenance
docker compose exec -T postgres dropdb -U omnara --force omnara
docker compose exec -T postgres createdb -U omnara omnara
docker compose exec -T postgres pg_restore -U omnara -d omnara --no-owner \
  < /tmp/omnara-restore/omnara.dump
docker run --rm -i -v omnara_rustfs-data:/rustfs-data -v omnara_memory-data:/memory-data \
  postgres:18-alpine sh -c 'find /rustfs-data /memory-data -mindepth 1 -delete && tar -C / -xf -' \
  < /tmp/omnara-restore/volumes.tar
docker compose up -d
rm -rf /tmp/omnara-restore
```

If the keys differ, as on a new VM, copy the backup's `.env` over
`~/omnara/.env` before `stop`, with the VM's name changed in
`OMNARA_PUBLIC_URL` and `OMNARA_EMAIL_FROM`. Connect the machines again afterwards.

</details>

## Update

Omnara moves fast: read the
[release notes](https://github.com/omnara-ai/omnara/releases) first. Then,
with the new release's number in place of `0.2.15`:

```bash
cd ~/omnara
./backup.sh
v=0.2.15
git fetch --depth 1 origin tag cluster-v$v && git checkout cluster-v$v
sed -i "s/^OMNARA_RELEASE=.*/OMNARA_RELEASE=v$v/" .env
docker compose up -d
```

The `migrate` service upgrades the database before the API starts. Newer
releases are untested here; if one misbehaves, restore the backup with
the old release checked out.

## Troubleshooting

- Logs: `cd ~/omnara && docker compose logs -f api worker`.
- After stopping the API or worker, use `docker compose up -d`, not
  `start` or `restart`: the relays share their network, and `up` brings
  them back too.
- A model call can't reach `127.0.0.1:8090`: the relay is down. Run
  `docker compose up -d`, then the relay check from
  [Install by hand](#create-the-vm).
- A model is refused as not in the integration's model list: your
  [LLM integration](/docs/integrations-llm) doesn't serve it. Pick
  another, or enable that provider in the integration.
- Links point to the wrong address, or the API refuses requests for your
  VM's name: `OMNARA_PUBLIC_URL` in `.env` must be
  `https://<vm>.exe.xyz`. Run `docker compose up -d` after changing it.
- No email arrives: `docker compose logs api-mail` shows `sent` or why
  exe.dev refused it, followed by the message. Only you, your team, and
  people the VM is shared with can get Omnara's email.
- The machine stays **Offline**:
  `sudo journalctl _UID=$(id -u omnara-agent) -n 30` and
  `sudo -iu omnara-agent .omnarad/bin/omnarad status`.

## Frequently asked questions

### How does this compare with Claude Managed Agents and the OpenAI Agents API?

With those, the provider runs the agent loop, sessions, and memory, and you bring at most the machines its tools run on; see [Claude Managed Agents](/deploy/claude-managed-agents) and the [OpenAI Agents SDK](/deploy/openai-agents-sdk). Omnara is the whole platform, open source, on your VM: agent configs, event logs, schedules, Slack, Discord and GitHub triggers, an API and SDK, with any model. In exchange you run and update it, and it's young (0.2).

### Do I need an Anthropic or OpenAI API key?

No. Agents call exe.dev's [LLM gateway](/docs/integrations-llm) through a relay on the VM, and exe.dev adds the credentials on the way out. To use your own provider key or ChatGPT account, point your LLM integration at it; Omnara doesn't change.

### Can agents use Claude?

Yes, if your LLM integration serves Claude: add a second provider with the Anthropic preset and the same base URL, `http://127.0.0.1:8090/v1`.

### Why does .env say OMNARA_ALLOW_INSECURE_DEV_DEFAULTS?

Omnara's production mode refuses model endpoints on private or loopback addresses, which rules out the relay, and wants STARTTLS from its SMTP server, which the mail bridge (on loopback, inside the API's container) doesn't offer. Development mode allows both. Its built-in default keys only fill settings you leave empty, and this page sets real ones. The VM stays behind exe.dev login, so strangers never reach Omnara.

### Who can Omnara email?

Whoever exe.dev's [email gateway](/docs/send-email) allows: you, your exe.dev team, and people you've shared the VM with. That covers everyone who can reach Omnara behind exe.dev login. A refused message is written to the bridge's log, link included; see [Sign up](#sign-up).

### What can the agents do on the VM?

Run commands as omnara-agent, in /home/omnara-agent, with internet access and the integrations attached to the VM, but no sudo, no Docker, and no route to the stack's databases. For root, or for heavy work, give agents a machine of their own, such as one of the [sandbox providers](https://docs.omnara.com/machines/overview) Omnara supports.

### What does it cost?

Omnara is free and Apache-2.0. The VM is part of your [exe.dev plan](https://exe.dev/pricing), and agents' model calls use your exe.dev [LLM allocation](/docs/integrations-llm). The stack uses about 700 MB of memory and 6 GB of disk when idle.


## Related

- [Self-hosted sandbox for Claude Managed Agents](https://exe-staging.dev/deploy/claude-managed-agents.md): Anthropic runs the agent. Each session's tools run on a VM of its own.
- [Sandboxes for the OpenAI Agents SDK](https://exe-staging.dev/deploy/openai-agents-sdk.md): The SDK runs the agent loop. Each session's commands run on a VM of its own.
- [Running Agents](https://exe-staging.dev/deploy/agents.md): Use exe.dev VMs as a sandbox
- [Deploy a Docker Compose app from GitHub](https://exe-staging.dev/deploy/docker-compose.md): Your compose.yaml on a VM of its own. git push to deploy.
- [Self-host Open WebUI](https://exe-staging.dev/deploy/open-webui.md): A private ChatGPT for you and your team. No API keys.
- [Run Claude Code on a remote server](https://exe-staging.dev/deploy/claude-code.md): A computer for Claude. A breather for your laptop.
