---
title: "Self-host eve, Vercel's agent framework"
description: "Run an eve agent on an exe.dev VM instead of Vercel: durable sessions and schedules on disk, a Docker sandbox, exe.dev login, and GPT models with no API key."
url: https://exe-staging.dev/deploy/eve
category: "AI agents"
updated: 2026-11-13
---

# Self-host eve, Vercel's agent framework

Vercel's agent framework, off Vercel, with sessions and schedules on your own disk.

- **Software:** eve (https://eve.dev)
- **Source:** https://github.com/vercel/eve
- **License:** Apache-2.0
- **Time:** 10 minutes
- **Example VM name:** `my-eve` (yours will differ; on the VM, `hostname` prints it)
- **Machine-readable:** https://exe-staging.dev/deploy/eve/manifest.json
- **File:** https://exe-staging.dev/deploy/eve/agent.ts
- **File:** https://exe-staging.dev/deploy/eve/chat
- **File:** https://exe-staging.dev/deploy/eve/email_owner.ts
- **File:** https://exe-staging.dev/deploy/eve/eve.ts
- **File:** https://exe-staging.dev/deploy/eve/morning-brief.md

## What you get

- Node.js 26 and an eve project from eve init in ~/my-agent, a git repository, with eve pinned to 1.0.0-rc.14
- eve.service enabled, serving on 127.0.0.1:3000, private behind exe.dev login, and back after a reboot
- Model calls through exe.dev's LLM integration, with no OpenAI API key on the VM
- Commands the agent runs in a Docker container per session, not on the VM itself
- `./chat --print` answers from ~/my-agent
- The morning-brief schedule, which emails the owner five Hacker News stories at 08:00 UTC

Running on the VM:

- **Your agent**: An eve 1.0.0-rc.14 project from eve init, a git repository you edit, data in `~/my-agent/agent`
- **eve server**: eve start on Node.js 26, the eve systemd service, on 127.0.0.1 only, port 3000
- **Sessions and schedules**: Workflow's local world, files on the VM's disk, data in `~/my-agent/.eve/.workflow-data`
- **Sandbox**: A Docker container per session, from Vercel's ghcr.io/vercel/eve image
- **GPT models**: OpenAI, through exe.dev's LLM integration
- **Slack proxy (optional)**: nginx, passing only Slack's route through, port 8000

How it is reached:

- ssh my-eve.exe.xyz (private): the agent's chat, its project, and logs
- https://my-eve.exe.xyz:3000/eve/v1/ (private): eve's HTTP API, behind exe.dev login
- https://my-eve.exe.xyz/eve/v1/slack (public): Slack's events, only if you add Slack and make port 8000 public

## Deploy with Shelley

Open https://exe-staging.dev/new?prompt=Set%20up%20eve%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Feve.md%20from%20top%20to%20bottom.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-eve.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-eve.%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-%20Node.js%2026%20and%20an%20eve%20project%20from%20eve%20init%20in%20~%2Fmy-agent%2C%20a%20git%20repository%2C%20with%20eve%20pinned%20to%201.0.0-rc.14%0A-%20eve.service%20enabled%2C%20serving%20on%20127.0.0.1%3A3000%2C%20private%20behind%20exe.dev%20login%2C%20and%20back%20after%20a%20reboot%0A-%20Model%20calls%20through%20exe.dev%27s%20LLM%20integration%2C%20with%20no%20OpenAI%20API%20key%20on%20the%20VM%0A-%20Commands%20the%20agent%20runs%20in%20a%20Docker%20container%20per%20session%2C%20not%20on%20the%20VM%20itself%0A-%20%60.%2Fchat%20--print%60%20answers%20from%20~%2Fmy-agent%0A-%20The%20morning-brief%20schedule%2C%20which%20emails%20the%20owner%20five%20Hacker%20News%20stories%20at%2008%3A00%20UTC%0A%0ARun%20%22Set%20up%20eve%20by%20hand%22%20from%20%22On%20the%20VM%22%2C%20skipping%20VM%20creation%20and%0ASSH%2C%20then%20%22Email%20yourself%20a%20morning%20brief%22.%20Wait%20for%20%2Feve%2Fv1%2Fhealth%0Abefore%20each%20request.%20Don%27t%20wait%20for%2008%3A00%3A%20the%20hello%20email%20is%20the%0Acheck.%20Skip%20%22Answer%20in%20Slack%22%20unless%20the%20owner%20asks%20and%0Agives%20you%20the%20token%20and%20signing%20secret%3B%20never%20make%20port%208000%20public%0Ayourself.%20Finish%20with%20%60ssh%20-t%20%3Cvm%3E.exe.xyz%20my-agent%2Fchat%60%2C%20using%20this%0AVM%27s%20name.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open.%0A to create a VM whose agent, Shelley, follows this recipe. From a terminal:

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

Or follow the steps below by hand.

[eve](https://eve.dev) is Vercel's open-source TypeScript framework for
agents. An agent is a directory: instructions in Markdown, tools in
TypeScript, skills, schedules, and channels such as Slack. Each
conversation is a durable session that survives restarts, and the agent
runs commands in a sandbox. eve is built to deploy on Vercel, but
`eve build` also makes a plain Node.js server that runs anywhere.

An exe.dev VM runs that server around the clock under systemd, private
behind exe.dev login, with sessions and schedules in files on its disk
and each session's sandbox in Docker on the same VM. Models come from
exe.dev's [LLM integration](/docs/integrations-llm), so no API key is on
the VM, and the agent can email you without mail credentials.

## Set up eve

Deploy sets it up on a new VM. To do it yourself:

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

On your computer (pick any free VM name):

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

On the VM, install Node.js 26, start Docker at boot, and make a project
with eve's starter agent, pinned to this version (about a minute):

```bash
curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo systemctl enable --now docker.service
npx -y eve@1.0.0-rc.14 init my-agent --non-interactive
cd ~/my-agent
npm install --save-exact eve@1.0.0-rc.14
```

Point OpenAI models at the LLM integration, let exe.dev login stand in
for eve's auth, and add the `chat` script. These replace the starter's
`agent/agent.ts` and `agent/channels/eve.ts`, whose placeholder auth
refuses everyone in production:

```bash
(umask 077; printf 'OPENAI_BASE_URL=https://llm.int.exe.xyz/v1\nOPENAI_API_KEY=exe.dev\n' > .env)
curl -fsSL -o agent/agent.ts https://exe-staging.dev/deploy/eve/agent.ts
curl -fsSL -o agent/channels/eve.ts https://exe-staging.dev/deploy/eve/eve.ts
curl -fsSL -o chat https://exe-staging.dev/deploy/eve/chat && chmod +x chat
npx eve build | tail -n 1
```

The build ends with `built output at /home/exedev/my-agent/.output`. The
first one also builds the sandbox image, about 550 MB.

Run it under systemd on `127.0.0.1:3000`. Port 3000 is private through
exe.dev's [HTTPS proxy](/docs/proxy), and binding to `127.0.0.1` keeps
the sandboxes from reaching it:

```bash
sudo tee /etc/systemd/system/eve.service >/dev/null <<'EOF'
[Unit]
Description=eve agent
After=network-online.target docker.service
Wants=network-online.target docker.service

[Service]
User=exedev
WorkingDirectory=/home/exedev/my-agent
EnvironmentFile=/home/exedev/my-agent/.env
Environment=NODE_ENV=production
ExecStart=/home/exedev/my-agent/node_modules/.bin/eve start --host 127.0.0.1 --port 3000
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now eve
```

Check it after a few seconds with `curl -s localhost:3000/eve/v1/health`.
Expect `{"ok":true,"status":"ready",...}`. Then ask it something that
needs the sandbox:

```bash
./chat --print "Write /workspace/primes.py to print the first 10 primes, run it, and reply with its output only."
```

Expect `2 3 5 7 11 13 17 19 23 29` after about ten seconds.

</details>

## Chat with your agent

On your computer:

```bash
ssh -t my-eve.exe.xyz my-agent/chat
```

That opens eve's terminal chat against the server on the VM. The starter
agent answers with GPT through the LLM integration and runs `bash`, reads
and writes files, and fetches web pages in its sandbox, a Docker
container of its own for each session. `/help` lists the chat's
commands, and sessions carry on where they left off after a restart or a
reboot.

The project is yours to change: the agent's instructions in
`agent/instructions.md`, tools in `agent/tools/`, skills in
`agent/skills/`, and schedules in `agent/schedules/`. Shelley, or any
coding agent, can make the changes; the project's `AGENTS.md` tells it
how. After a change, on the VM:

```bash
cd ~/my-agent
npx eve build | tail -n 1 && sudo systemctl restart eve
```

## Email yourself a morning brief

[email_owner.ts](https://exe-staging.dev/deploy/eve/email_owner.ts) is a tool
that emails you, the VM's owner, through exe.dev's
[email gateway](/docs/send-email): no address to configure and no mail
credentials. [morning-brief.md](https://exe-staging.dev/deploy/eve/morning-brief.md)
is a schedule that has the agent read Hacker News at 08:00 UTC every day
and email you the five stories that matter most to someone building with
agents. On the VM:

```bash
cd ~/my-agent
curl -fsSL --create-dirs -o agent/tools/email_owner.ts https://exe-staging.dev/deploy/eve/email_owner.ts
curl -fsSL --create-dirs -o agent/schedules/morning-brief.md https://exe-staging.dev/deploy/eve/morning-brief.md
npx eve build | tail -n 1 && sudo systemctl restart eve
```

`eve start` fires schedules itself, so nothing else needs a cron job.
Try the tool now:

```bash
./chat --print "Email me a one-line hello with email_owner."
```

Expect the email within a minute. To change the time or the prompt, edit
`agent/schedules/morning-brief.md` (`cron` is in `timezone`), then
rebuild and restart. Each run is a session of its own; the agent's reply
is discarded, so a schedule acts through tools such as `email_owner`.

## Answer in Slack

eve's Slack channel takes Slack's events over HTTPS, so Slack must reach
the VM. Port 8000 is the only port exe.dev can make
[public](/docs/sharing), so put nginx there, passing through only
`/eve/v1/slack`. eve checks every request's Slack signature; the rest of
eve stays behind exe.dev login on port 3000.

In [Slack's app settings](https://api.slack.com/apps), choose **Create
New App**, **From a manifest**, pick your workspace, and paste this,
with your VM's name:

```yaml
display_information:
  name: eve
features:
  bot_user:
    display_name: eve
    always_online: true
  app_home:
    messages_tab_enabled: true
    messages_tab_read_only_enabled: false
oauth_config:
  scopes:
    bot: [app_mentions:read, chat:write, channels:history, groups:history, im:history, mpim:history, files:read, files:write]
settings:
  event_subscriptions:
    request_url: https://my-eve.exe.xyz/eve/v1/slack
    bot_events: [app_mention, message.channels, message.groups, message.im, message.mpim]
  interactivity:
    is_enabled: true
    request_url: https://my-eve.exe.xyz/eve/v1/slack
```

Slack can't verify the Request URL yet; that comes last. Install the app
to the workspace, then copy the **Bot User OAuth Token** (`xoxb-...`,
under **OAuth & Permissions**) and the **Signing Secret** (under **Basic
Information**). On the VM, add the Slack channel and both secrets, and
rebuild:

```bash
cd ~/my-agent
npx eve add channel/slack --non-interactive --skip-install --answer 'slack-credentials="portable"'
read -rsp 'Bot token: ' T; echo; read -rsp 'Signing secret: ' S; echo
printf 'SLACK_BOT_TOKEN=%s\nSLACK_SIGNING_SECRET=%s\n' "$T" "$S" >> .env; unset T S
npx eve build | tail -n 1 && sudo systemctl restart eve
```

Then serve only Slack's route on port 8000. exeuntu's default nginx site
also listens there, so remove it:

```bash
sudo tee /etc/nginx/sites-available/eve-slack >/dev/null <<'EOF'
# Port 8000 is the only port exe.dev can make public.
# Serve Slack's route there and nothing else; eve stays private on :3000.
server {
    listen 8000 default_server;
    listen [::]:8000 default_server;
    location = /eve/v1/slack {
        proxy_pass http://127.0.0.1:3000/eve/v1/slack;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
    }
    location / {
        return 404;
    }
}
EOF
sudo ln -sf /etc/nginx/sites-available/eve-slack /etc/nginx/sites-enabled/eve-slack
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl enable nginx && sudo systemctl restart nginx
```

Once eve is back up, check the route:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/eve/v1/slack
```

Expect `401`, eve refusing an unsigned request; `502` means eve is still
starting. Then, on
your computer, make port 8000 public:

```bash
ssh exe.dev share set-public my-eve
```

Back in Slack, under **Event Subscriptions**, click **Retry** next to the
Request URL; expect **Verified**. Invite the bot to a channel
(`/invite @eve`) and mention it. It replies in a thread, and each thread
is a session. `ssh exe.dev share set-private my-eve` takes it off the
internet again.

## Back up and restore

Everything is in `~/my-agent`: the project in git, `.env` (its secrets),
and `.eve/.workflow-data` (sessions, their history, and schedule state).
Stop eve for a consistent copy. On the VM:

```bash
cd ~
sudo systemctl stop eve
tar czf eve-backup-$(date +%F).tgz --exclude=my-agent/node_modules --exclude=my-agent/.output my-agent
sudo systemctl start eve
```

Copy the file off the VM, for example with
`scp my-eve.exe.xyz:eve-backup-*.tgz .` on your computer. The sandbox
containers aren't in it; a restored session starts a fresh one.

To restore on a VM set up as above, with the same eve version:

```bash
sudo systemctl stop eve
cd ~ && rm -rf my-agent && tar xzf eve-backup-*.tgz
cd my-agent && npm ci && npx eve build | tail -n 1
sudo systemctl start eve
```

## Update

eve is a release candidate and changes often; read the
[releases](https://github.com/vercel/eve/releases) before you move. A
server on a new eve version can't continue sessions started on the old
one: they answer `409 session_stranded`, so finish or abandon them
first. Rebuilding on the same version resumes them. On the VM, after a
backup:

```bash
cd ~/my-agent
npm install --save-exact eve@<version>
npx eve build | tail -n 1 && sudo systemctl restart eve
curl -s localhost:3000/eve/v1/health
```

## Troubleshooting

- `sudo journalctl -u eve -f` shows the server's log; build errors appear
  in `npx eve build`, not there. Drop `| tail -n 1` to see all of it.
- `401 unauthorized` from `chat` or the API: the request lacked exe.dev's
  login headers. Use `./chat` on the VM, or a token from your computer.
- `Item with id 'rs_...' not found`: the model's reasoning was sent by
  reference, which the LLM integration can't keep. Check that
  `agent/agent.ts` has `store: false` and `reasoning.encrypted_content`.
- `Connection error` or `401` from the model: check `.env`. The LLM
  integration is reachable only from exe.dev VMs.
- `DefaultSandbox has no sandbox provider for this build`: `eve build`
  couldn't reach Docker. Run `sudo systemctl enable --now docker.service`
  and rebuild.
- Slack never verifies the Request URL: `curl -s -X POST
  localhost:8000/eve/v1/slack` should give `unauthorized`, and
  `ssh exe.dev share show my-eve` should say public. A `401` in the
  eve log for Slack's requests means the signing secret in `.env` is
  wrong.

## Frequently asked questions

### Do I need Vercel, or an OpenAI API key?

Neither. `eve build` makes a plain Node.js server and `eve start` runs it, with no Vercel account. `openai()` from `eve/models/openai` sends requests to `OPENAI_BASE_URL`, which `.env` points at exe.dev's [LLM integration](/docs/integrations-llm); it bills your exe.dev account, and the key in `.env` is a placeholder. A model given as a plain string, such as `"openai/gpt-5.5"`, goes to Vercel's AI Gateway instead and needs `AI_GATEWAY_API_KEY`.

### Who can talk to the agent?

You, over SSH or after exe.dev login. eve listens on `127.0.0.1`, and `agent/channels/eve.ts` admits only requests that carry the login headers exe.dev's [HTTPS proxy](/docs/proxy) adds, so a request without them gets `401`. Port 3000 is private; `ssh exe.dev share add my-eve ann@example.com` lets someone else in, with the same power over the agent as you.

### Can I connect from my own computer?

Yes, with an exe.dev [HTTPS token](/docs/https-tokens-for-vms) for this VM: `ssh exe.dev ssh-key generate-api-key --vm=my-eve --label=eve`, then `npx eve@1.0.0-rc.14 remote connect --url https://my-eve.exe.xyz:3000 -H "X-Exedev-Authorization: Bearer <token>"`. The proxy checks the token and adds the login headers. Your own code can call the [HTTP API](https://eve.dev/docs/concepts/sessions-runs-and-streaming) the same way.

### Where does the agent run its commands?

In a Docker container for each session, from Vercel's `ghcr.io/vercel/eve` image (Ubuntu, Node.js 26, Python 3), in `/workspace`. eve uses Docker because `eve build` found it. The container stops when the agent is idle and is deleted when the session ends, after 30 idle days by default. It can reach the internet, but not eve or anything else listening on the VM's `127.0.0.1`.

### How is this different from deploying eve on Vercel?

It's one always-on machine: `eve start` runs the server, its schedules, and the sandboxes, and keeps sessions in files on its disk, with no Vercel Sandbox, Queues, or database. That also means one process: for replicas, eve's [Postgres world](https://eve.dev/docs/guides/deployment/self-hosting) shares sessions between servers. There's no autoscaling; a bigger VM is the way up.

### Does eve send telemetry?

The CLI does, on by default. `npx eve telemetry disable` in `~/my-agent` turns it off, as does `EVE_TELEMETRY_DISABLED=1`.


## Related

- [Run Mastra agents and workflows on your own VM](https://exe-staging.dev/deploy/mastra.md): The TypeScript agent framework, with Studio, memory, and a VM per thread.
- [Host a Claude Agent SDK app on its own VM](https://exe-staging.dev/deploy/claude-agent-sdk.md): Claude Code as a library, with a whole computer to run on.
- [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.
- [Create sandboxes from TypeScript](https://exe-staging.dev/deploy/typescript-sandboxes.md): Full VMs as sandboxes. Create, control, and destroy them from your code.
- [Running Agents](https://exe-staging.dev/deploy/agents.md): Use exe.dev VMs as a sandbox
- [Run Claude Code on a remote server](https://exe-staging.dev/deploy/claude-code.md): A computer for Claude. A breather for your laptop.
