---
title: "Run Mastra agents and workflows on your own VM"
description: "Run Mastra agents and scheduled workflows on an exe.dev VM: the server and Studio under systemd, memory in SQLite, models from the LLM integration, and a VM per thread."
url: https://exe-staging.dev/deploy/mastra
category: "AI agents"
updated: 2026-10-31
---

# Run Mastra agents and workflows on your own VM

The TypeScript agent framework, with Studio, memory, and a VM per thread.

- **Software:** Mastra (https://mastra.ai)
- **Source:** https://github.com/mastra-ai/mastra
- **License:** Apache-2.0 (ee/ directories under the Mastra Enterprise License)
- **Time:** 10 minutes
- **Example VM name:** `my-mastra` (yours will differ; on the VM, `hostname` prints it)
- **Machine-readable:** https://exe-staging.dev/deploy/mastra/manifest.json
- **File:** https://exe-staging.dev/deploy/mastra/exe-sandbox.ts
- **File:** https://exe-staging.dev/deploy/mastra/hn-digest.ts
- **File:** https://exe-staging.dev/deploy/mastra/vm-agent.ts

## What you get

- Node.js 24 and a Mastra project from create-mastra in ~/my-agents, a git repository
- mastra.service enabled, serving the API and Studio on port 8000, private to the VM's owner, and back after a reboot
- Model calls through exe.dev's LLM integration, with no OpenAI API key on the VM
- Threads, memory, workflow runs, and schedules in ~/my-agents/mastra.db
- The hn-digest workflow, run once and scheduled for 07:00 UTC every day

Running on the VM:

- **Your project**: The create-mastra 1.37 starter, a git repository you edit, data in `~/my-agents/src`
- **Mastra server**: @mastra/core 1.81 on Node.js 24, with Studio, the mastra systemd service, port 8000
- **Storage**: Threads, memory, workflow runs, and schedules in SQLite (LibSQL); traces in DuckDB, data in `~/my-agents/mastra.db`
- **GPT models**: OpenAI, through exe.dev's LLM integration
- **Thread VMs (optional)**: One exe.dev VM per conversation, through the mastra-vms integration

How it is reached:

- https://my-mastra.exe.xyz/ (private): Studio, to chat with agents, run workflows, and read traces
- https://my-mastra.exe.xyz/api/ (private): Mastra's HTTP API, behind the same exe.dev login
- ssh my-mastra.exe.xyz (private): the project, its data, and logs

## Deploy with Shelley

Open https://exe-staging.dev/new?prompt=Set%20up%20Mastra%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Fmastra.md%20from%20top%20to%20bottom.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-mastra.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-mastra.%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%2024%20and%20a%20Mastra%20project%20from%20create-mastra%20in%20~%2Fmy-agents%2C%20a%20git%20repository%0A-%20mastra.service%20enabled%2C%20serving%20the%20API%20and%20Studio%20on%20port%208000%2C%20private%20to%20the%20VM%27s%20owner%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-%20Threads%2C%20memory%2C%20workflow%20runs%2C%20and%20schedules%20in%20~%2Fmy-agents%2Fmastra.db%0A-%20The%20hn-digest%20workflow%2C%20run%20once%20and%20scheduled%20for%2007%3A00%20UTC%20every%20day%0A%0ARun%20%22Set%20up%20Mastra%20by%20hand%22%20from%20%22On%20the%20VM%22%2C%20skipping%20VM%20creation%20and%0ASSH%2C%20then%20%22Add%20a%20scheduled%20workflow%22.%20Wait%20for%20%2Fhealth%20before%20each%0Acurl%20to%20the%20server.%20Skip%20%22Give%20each%20conversation%20a%20VM%22%20unless%20the%20owner%0Aasks%3A%20only%20they%20can%20create%20its%20integration%2C%20from%20their%20own%20computer%3B%0Anever%20ask%20for%20an%20exe.dev%20or%20OpenAI%20API%20key%20instead.%20Keep%20port%208000%0Aprivate%3A%20never%20run%20share%20set-public.%20Finish%20with%20https%3A%2F%2F%3Cvm%3E.exe.xyz%2F%2C%0Ausing%20this%20VM%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/mastra/prompt.txt | ssh exe.dev new --prompt=/dev/stdin
```

Or follow the steps below by hand.

[Mastra](https://mastra.ai) is a TypeScript framework for AI agents and
workflows: agents with tools, memory, and a workspace to run commands in;
workflows of typed steps, which can run on a schedule; and Studio, a web
UI to chat with agents, run workflows, and read traces. `mastra build`
turns a project into one Node.js server that serves its HTTP API and
Studio.

An exe.dev VM runs that server around the clock under systemd, private
behind exe.dev login, with its memory and schedules in a SQLite file on
the VM's disk. Models come from exe.dev's
[LLM integration](/docs/integrations-llm), so no API key is on the VM.
And an agent can get a VM of its own for each conversation, made through
exe.dev's API, to install, build, and serve whatever it likes.

## Set up Mastra

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 Mastra by hand</summary>

On your computer:

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

On the VM, install Node.js 24 and make a project with Mastra's starter
agent (about a minute):

```bash
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs
npx -y create-mastra@1 my-agents --llm openai < /dev/null
```

Point OpenAI models at the LLM integration, and build the server with
Studio:

```bash
cd ~/my-agents
(umask 077; printf 'OPENAI_BASE_URL=https://llm.int.exe.xyz/v1\nOPENAI_API_KEY=exe.dev\n' > .env)
npm run build -- --studio
```

Run it under systemd on port 8000, the VM's HTTPS address.
`MASTRA_AUTO_DETECT_URL` makes Studio call the API at the address it was
opened from, rather than `localhost`:

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

[Service]
User=exedev
WorkingDirectory=/home/exedev/my-agents
EnvironmentFile=/home/exedev/my-agents/.env
Environment=NODE_ENV=production PORT=8000 MASTRA_STUDIO_PATH=.mastra/output/studio MASTRA_AUTO_DETECT_URL=true
ExecStart=/usr/bin/node .mastra/output/index.mjs
Restart=on-failure

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

Check it after a few seconds with `curl -s localhost:8000/health`. Expect
`{"success":true}`.

</details>

## Open Studio

Open `https://my-mastra.exe.xyz/` and sign in to exe.dev. Under
**Agents**, the starter agent chats with GPT through the LLM integration,
searches and fetches the web, keeps memory across threads, and reads,
writes, and runs commands in `~/my-agents/workspace` on this VM; it asks
before deleting anything. **Workflows**, **Traces**, and **Logs** are in
the sidebar.

The project is yours to change: agents in `src/mastra/agents`, tools in
`src/mastra/tools`, and everything registered in `src/mastra/index.ts`.
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-agents
npm run build -- --studio && sudo systemctl restart mastra
```

## Add a scheduled workflow

[hn-digest.ts](https://exe-staging.dev/deploy/mastra/hn-digest.ts) is a workflow
of two steps: fetch the top Hacker News stories, then have a model write
a short digest to `workspace/digests/<date>.md`, where the starter agent
can read it. It runs every day at 07:00 UTC. On the VM, add it and
register it:

```bash
cd ~/my-agents
curl -fsSL --create-dirs -o src/mastra/workflows/hn-digest.ts https://exe-staging.dev/deploy/mastra/hn-digest.ts
sed -i "s|^import { agent } from './agents/agent';|&\nimport { hnDigest } from './workflows/hn-digest';|; s|^  agents: { agent },|&\n  workflows: { hnDigest },|" src/mastra/index.ts
grep -c hnDigest src/mastra/index.ts
npm run build -- --studio && sudo systemctl restart mastra
```

Expect `2` from `grep`. Once `/health` answers, run it now rather than
at 07:00:

```bash
curl -s -X POST localhost:8000/api/workflows/hn-digest/start-async \
  -H 'Content-Type: application/json' -d '{"inputData":{"count":5}}'
```

Expect `"status":"success"` and the digest, after about ten seconds.
Then ask the starter agent in Studio for today's digest. Studio lists the
schedule at `https://my-mastra.exe.xyz/workflows/schedules`, with
**Pause** and **Resume**; it's kept in `mastra.db`, so it survives
restarts.

The HTTP API answers the same way your own code calls it. From this VM:

```bash
curl -s localhost:8000/api/agents/agent/generate -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Hi"}],"memory":{"thread":"t1","resource":"me"}}'
```

From another of your exe.dev VMs, use a
[VM-to-VM integration](/docs/integrations-vm-to-vm) to `my-mastra`,
which keeps port 8000 private.

## Give each conversation a VM

The starter agent runs commands on the Mastra VM itself. For agents that
install packages, run Docker, or serve apps, give each thread a VM of its
own instead.
[exe-sandbox.ts](https://exe-staging.dev/deploy/mastra/exe-sandbox.ts) is a
Mastra workspace sandbox that is an exe.dev VM, made with exe.dev's
[TypeScript SDK](https://www.npmjs.com/package/@exedev/sdk) on the
thread's first command and kept until you delete it.
[vm-agent.ts](https://exe-staging.dev/deploy/mastra/vm-agent.ts) is an agent that
uses one per thread.

The Mastra VM makes those VMs through an integration that holds the API
key: no key is ever on a VM. On your computer:

```bash
ssh exe.dev integrations add exe-dev --name mastra-vms \
  --tag mastra-sandbox --cmds new,ls,ssh,rm --attach vm:my-mastra
```

The integration can make, reach, and delete VMs tagged `mastra-sandbox`,
and nothing else; the VMs it makes get that tag. Only the Mastra VM can
use it. On the VM, check it, then add the two files and register the
agent:

```bash
curl -s -X POST https://mastra-vms.int.exe.xyz/exec -d ls
cd ~/my-agents
npm install @exedev/sdk@0.4
curl -fsSL -o src/mastra/exe-sandbox.ts https://exe-staging.dev/deploy/mastra/exe-sandbox.ts
curl -fsSL -o src/mastra/agents/vm-agent.ts https://exe-staging.dev/deploy/mastra/vm-agent.ts
sed -i "s|^import { agent } from './agents/agent';|&\nimport { vmAgent } from './agents/vm-agent';|; s|^  agents: { agent },|  agents: { agent, vmAgent },|" src/mastra/index.ts
grep -c vmAgent src/mastra/index.ts
npm run build -- --studio && sudo systemctl restart mastra
```

Expect `{"vms":[]}` from the check and `2` from `grep`. In Studio, open
**VM Agent** and ask: "Make a small Flask to-do app, serve it on port
8080, and give me the link." The first command takes a few seconds while
the VM starts. The link, `https://mastra-<id>.exe.xyz:8080/`, opens for
you after exe.dev login. A later message in the same thread finds the
same VM, with its files and running services; a new thread gets a new
one.

> [!WARNING]
> VM Agent runs commands with sudo, without asking. That's what the
> thread VMs are for: they hold nothing else. Keep the Mastra VM, which
> can make VMs on your account, to yourself, and never attach the
> integration to the `mastra-sandbox` tag, which would let thread VMs do
> the same.

To use the sandbox in your own agents, pass a resolver as `vm-agent.ts`
does, or a fixed one: `sandbox: new ExeSandbox({ id: 'builds' })`.
`ExeSandbox` also takes `workingDirectory`, `timeout`, and `env`. Off
exe.dev, pass `token:` an exe.dev API key from
`ssh exe.dev ssh-key generate-api-key --label=mastra --tag=mastra-sandbox --cmds=new,ls,ssh,rm --exp=30d`.

## Clean up

Thread VMs stay until you delete them; each counts toward your plan's VM
limit. On your computer, `ssh exe.dev ls` lists them, with their thread
as the comment. To delete every VM tagged `mastra-sandbox`:

```bash
ssh exe.dev ls --json |
  jq -r '.vms[] | select(.tags // [] | index("mastra-sandbox")) | .vm_name' |
  xargs -r ssh exe.dev rm
```

When you're done with them,
`ssh exe.dev integrations remove mastra-vms`. `ssh exe.dev rm my-mastra`
deletes the Mastra VM and its data.

## Troubleshooting

- Studio loads but can't reach the server, or asks for an instance URL:
  check that the unit has `MASTRA_AUTO_DETECT_URL=true`, then
  `sudo systemctl daemon-reload && sudo systemctl restart mastra`.
- `sudo journalctl -u mastra -f` shows the server's log; build errors
  appear in `npm run build`, not there.
- `Connection error` or `401` from the model: check `.env`. The LLM
  integration is reachable only from exe.dev VMs.
- VM Agent fails with `403` and `integration not found`: run the command
  in [Give each conversation a VM](#give-each-conversation-a-vm) with this
  VM's name, or
  `ssh exe.dev integrations attach mastra-vms vm:<this-vm>`.
- VM Agent fails during `new`: exe.dev refused a VM, often because the
  plan's VM limit is reached. Delete old thread VMs.

## Frequently asked questions

### Do I need an OpenAI API key?

No. Mastra's model router sends `openai/...` models 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. For other providers, put their key in `.env` and use their model ids, such as `anthropic/...`.

### Who can open Studio?

Only you, after exe.dev login: port 8000 is private through the [HTTPS proxy](/docs/proxy). Studio has full access to your agents, workflows, and tools, so share it only with people you'd give a shell on the VM: `ssh exe.dev share add my-mastra ann@example.com`. Mastra's own [Studio auth](https://mastra.ai/docs/studio/auth) isn't needed.

### Where is my data, and how do I back it up?

On the VM's disk, in `~/my-agents`: `mastra.db` (SQLite: threads, messages, memory, workflow runs, and schedules), `mastra.duckdb` (traces), and `workspace/` (the starter agent's files). `sqlite3 mastra.db ".backup mastra-backup.db"` copies the database while the server runs. For Postgres instead, see [PostgreSQL](/deploy/postgres) and Mastra's `@mastra/pg`.

### How is this different from the Mastra platform, Vercel, or Railway?

It's one always-on machine, not a function: schedules, background tasks, and the SQLite file all live on it, and nothing needs a separate database or cron service. It's private until you share it. There's no autoscaling; a bigger VM (`ssh exe.dev resize`) is the way up.

### How does the VM-per-thread sandbox compare with E2B or Daytona?

It plugs into the same place, a `Workspace` sandbox resolved per thread. Each sandbox is a whole Ubuntu VM with sudo, Docker, and systemd, so an agent can install anything and serve web apps at a private HTTPS address, and the VM is still there days later. Thread VMs share your plan's CPU and memory, but each counts toward its VM limit. `exe-sandbox.ts` doesn't do mounts or Mastra's background-process tools; agents run servers under systemd instead.

### Does Mastra send telemetry?

The CLI and Studio collect anonymous usage analytics. Add `MASTRA_TELEMETRY_DISABLED=1` to `.env` and rebuild to turn them off. The starter's Mastra platform exporter sends traces only once you configure a platform access token.


## Related

- [Host a Claude Agent SDK app in a sandbox 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.
- [Self-host a Cursor Agent SDK agent](https://exe-staging.dev/deploy/cursor-agent-sdk.md): Cursor's agents-as-code framework, on a machine you run.
- [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.
