Deployment
Agent-native apps use Nitro under the hood, which means you can deploy to any platform with zero config changes — just set a preset.
Workspace Deploy: One Origin, Many Apps
If your project is a workspace, you can ship every app in it to a single origin with one command:
agent-native deploy
# https://your-agents.com/mail/* → apps/mail
# https://your-agents.com/calendar/* → apps/calendar
# https://your-agents.com/forms/* → apps/formsEach app is built with APP_BASE_PATH=/<name> and VITE_APP_BASE_PATH=/<name>, then packaged into dist/<name>/. Cloudflare Pages is the default preset and uses a generated dispatcher worker at dist/_worker.js; Netlify uses one function per app in .netlify/functions-internal/<app>-server plus generated redirects.
Same-origin deploy gives you two big wins for free:
- Shared login session — log into any app, every app is logged in.
- Zero-config cross-app A2A — tagging
@calendarfrom mail is a same-origin fetch; no CORS, no JWT signing between siblings.
Publish the output with:
wrangler pages deploy distFor Netlify unified deploys, use the Netlify preset:
agent-native deploy --preset netlifyGenerated workspaces include a root netlify.toml that runs agent-native deploy --preset netlify --build-only, publishes dist, and points Netlify at .netlify/functions-internal.
Hosted workspace builds require A2A_SECRET in the deploy provider environment.
This makes Slack, inbound webhooks, and cross-app A2A resume work through signed
background processors. Local --build-only artifact checks still run without it.
Per-app independent deploy is still supported — just cd apps/<name> && agent-native build like a standalone scaffold.
How It Works
When you run agent-native build, Nitro builds both the client SPA and the server API into .output/:
.output/
public/ # Built SPA (static assets)
server/
index.mjs # Server entry point
chunks/ # Server code chunksThe output is self-contained — copy .output/ to any environment and run it.
Setting the Preset
By default, Nitro builds for Node.js. To target a different platform, set the preset in your vite.config.ts:
import { defineConfig } from "@agent-native/core/vite";
export default defineConfig({
nitro: {
preset: "vercel",
},
});Or use the NITRO_PRESET environment variable at build time:
NITRO_PRESET=netlify agent-native buildNode.js (Default)
The default preset. Build and run:
agent-native build
node .output/server/index.mjsSet PORT to configure the listen port (default: 3000).
Use the current Node.js LTS line for production deploys. As of May 2026, that is Node.js 24; Node.js 20 reached end-of-life on April 30, 2026 and no longer receives upstream security updates.
Docker
FROM node:24-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:24-slim
WORKDIR /app
COPY --from=build /app/.output .output
COPY --from=build /app/data data
ENV PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]Vercel
// vite.config.ts
export default defineConfig({
nitro: { preset: "vercel" },
});Deploy via the Vercel CLI or git push:
vercel deployNetlify
The Nitro netlify preset works well and, in practice, has given us much faster cold starts than Cloudflare Pages (~200ms TTFB vs ~9s) for templates that talk to external Postgres (Neon). Either set the preset in vite.config.ts:
// vite.config.ts
export default defineConfig({
nitro: { preset: "netlify" },
});…or set NITRO_PRESET=netlify at build time.
For a workspace, deploy every app from one Netlify site by running:
agent-native deploy --preset netlifyThe workspace build writes static assets to dist/<app>/ and routes each app to its own Netlify function without forced redirects, so files like /mail/assets/... are served statically before the server function handles app routes.
Cloudflare Pages
// vite.config.ts
export default defineConfig({
nitro: { preset: "cloudflare_pages" },
});AWS Lambda
// vite.config.ts
export default defineConfig({
nitro: { preset: "aws_lambda" },
});Deno Deploy
// vite.config.ts
export default defineConfig({
nitro: { preset: "deno_deploy" },
});Environment Variables
Build / Runtime
| Variable | Description |
|---|---|
PORT |
Server port (Node.js only) |
NITRO_PRESET |
Override build preset at build time |
APP_BASE_PATH |
Mount the app under a prefix (e.g. /mail). Set automatically by agent-native deploy; leave unset for standalone. |
DATABASE_URL |
Postgres / Turso / SQLite connection string. Required in production. |
DATABASE_AUTH_TOKEN |
Auth token for Turso / libsql connections. |
Required in Production
These must be set before promoting an app to a real prod deploy. Missing values either fail-closed (the framework refuses to start / refuses to handle requests) or fall back to weaker behavior with a loud warning.
| Variable | Description |
|---|---|
BETTER_AUTH_SECRET |
32+ char random string. Signs session cookies AND is the fallback HMAC for OAUTH_STATE_SECRET and SECRETS_ENCRYPTION_KEY. Hard-required: the framework throws on startup if missing in production. |
BETTER_AUTH_URL |
Public origin of this app (e.g. https://mail.example.com). Used for cookie domain and OAuth redirect construction. |
ANTHROPIC_API_KEY |
API key for the embedded production agent. In multi-tenant deploys, the framework refuses to fall back to this when the user has no per-user key — bring-your-own-key is required. Single-tenant self-hosted installs use it as a global key. |
OAUTH_STATE_SECRET |
Dedicated HMAC key for OAuth state envelopes (Google, Atlassian, Zoom). Falls back to BETTER_AUTH_SECRET when unset, but a dedicated value is recommended so rotating one doesn't invalidate the other. Generate via openssl rand -hex 32. |
A2A_SECRET |
Shared HMAC for inter-app A2A JSON-RPC. Without it, every A2A endpoint and the /_agent-native/integrations/process-task self-fire endpoint return 503 in production. |
SECRETS_ENCRYPTION_KEY |
AES-256-GCM key for the encrypted-at-rest secrets vault. Falls back to BETTER_AUTH_SECRET. Hard-fails in production when both are unset. |
Auth & Identity
| Variable | Description |
|---|---|
ACCESS_TOKEN |
Single shared token for simple production deploys (alternative to Better Auth). |
ACCESS_TOKENS |
Comma-separated list of access tokens. |
AUTH_SKIP_EMAIL_VERIFICATION |
Skip email verification for QA accounts. Local dev/test skips by default; hosted deploys must set this explicitly. Disables a real security control — only use on hosted QA environments. |
GOOGLE_CLIENT_ID |
Google OAuth client ID. Auto-enables "Sign in with Google" in Better Auth. |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret. |
GITHUB_CLIENT_ID |
GitHub OAuth client ID. |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret. |
Inbound Webhooks
Inbound webhook handlers refuse forged requests when their signing secret is missing in production (was previously fail-open with a warning — see CHANGELOG / security audit fixes).
| Variable | Required when |
|---|---|
EMAIL_INBOUND_WEBHOOK_SECRET |
Inbound email integration is enabled. Verifies Resend / SendGrid / Svix signatures. |
TELEGRAM_WEBHOOK_SECRET |
Telegram bot integration is enabled. |
WHATSAPP_APP_SECRET |
WhatsApp Business integration is enabled. |
WHATSAPP_VERIFY_TOKEN |
WhatsApp webhook verification handshake (set in your Meta app dashboard too). |
SLACK_SIGNING_SECRET |
Slack integration is enabled. Verifies Slack request signatures. |
RECALL_WEBHOOK_SECRET |
Calls / Recall.ai integration is enabled. |
DEEPGRAM_WEBHOOK_SECRET |
Calls / Deepgram transcription webhook is enabled. |
ZOOM_WEBHOOK_SECRET |
Calls / Zoom integration is enabled. |
GOOGLE_DOCS_PUSH_AUDIENCE |
Google Docs Pub/Sub push integration is enabled. Set to the public URL of your push endpoint. |
GOOGLE_DOCS_PUSH_SIGNER_EMAIL |
Google Docs Pub/Sub push integration is enabled. Set to the Pub/Sub service account email. |
GMAIL_WATCH_TOPIC |
Gmail Pub/Sub push (mail template). Optional — disables push if unset and falls back to history-delta polling. |
GMAIL_PUSH_AUDIENCE |
Gmail Pub/Sub push audience. |
GMAIL_PUSH_SIGNER_EMAIL |
Gmail Pub/Sub push signer email. |
For local development of any of these integrations, set AGENT_NATIVE_ALLOW_UNVERIFIED_WEBHOOKS=1 to opt back into the old "warn and accept" behavior — never set this in prod.
Security Configuration (Opt-in)
Defaults are strict; these flags relax behavior. Don't set them unless you specifically want the relaxed path.
| Variable | Effect |
|---|---|
AGENT_NATIVE_DEBUG_ERRORS |
=1 to include stack traces in 500 JSON responses. Useful on previews; do not set in real prod (was previously gated by NODE_ENV !== "production", which leaked stacks on misconfigured deploys). |
AGENT_NATIVE_ALLOW_UNVERIFIED_WEBHOOKS |
=1 to accept webhooks without their signing secret (local dev only). Defaults to fail-closed in production. |
AGENT_NATIVE_KEYS_WORKSPACE_FALLBACK |
=1 to let ${keys.NAME} resolution in tools/automations fall through user-scope → workspace-scope. Default off (user-scope only) — a malicious org member could otherwise plant a workspace OPENAI_API_KEY and harvest other members' calls. Turn on only if your org genuinely shares workspace-wide keys. |
AGENT_NATIVE_MCP_HUB_MULTI_ORG |
=1 to allow AGENT_NATIVE_MCP_HUB_TOKEN to serve multiple orgs from a single hub deployment. Default refuses to serve when more than one org exists in a hub deploy. Only relevant if you operate the workspace MCP hub. |
AGENT_NATIVE_ALLOW_ENV_VAR_WRITES |
=1 to let runtime code mutate process.env from the env-var write API. Off by default — required to be explicitly enabled outside dev SQLite. |
AUTH_SKIP_EMAIL_VERIFICATION |
=1 to skip email verification for password signups. Local dev/test skips by default; hosted deploys should use this only for QA — see Auth section above. |
Workspace .env Inheritance
Inside a workspace, the root .env is loaded into every app automatically, so shared keys like ANTHROPIC_API_KEY, A2A_SECRET, BETTER_AUTH_SECRET, and OAUTH_STATE_SECRET only need to be set once. Per-app apps/<name>/.env wins on conflict.
Generating Strong Secrets
For any secret marked "32+ char random" (BETTER_AUTH_SECRET, OAUTH_STATE_SECRET, A2A_SECRET, SECRETS_ENCRYPTION_KEY), generate fresh values with:
openssl rand -hex 32Rotate them by replacing the env var on every instance and redeploying — sessions / OAuth state envelopes signed under the old key become invalid, so users may need to sign in again.
Updating UI in Production
One of agent-native's core features is that the agent can modify your app's source code — components, routes, styles, actions. During local development this works seamlessly because the agent has full filesystem access.
In a standard production deployment, however, the agent runs in production mode with access to app tools (actions, database, MCP) but not the filesystem. This means the agent can read and write data, run actions, and interact with external services — but it can't edit your React components or add new routes on a deployed instance.
Builder.io: Visual Editing in Production
Builder.io solves this by providing a managed cloud environment where the agent retains the ability to modify your app's UI in production. Connect your repo to Builder.io and prompt for UI changes directly — no redeploy needed.
How it works:
- Connect your agent-native repo to Builder.io
- Builder.io provides a cloud frame with the agent, visual editing, and real-time collaboration
- Prompt the agent to make UI changes — it edits your components, routes, and styles live
- Changes are committed back to your repo
See Frames for more on the embedded agent panel vs. cloud frame options.
Multi-instance deploys
Agent-native apps store all state in SQL via Drizzle and sync the UI via polling against the database — no file-system state, no sticky sessions, no in-memory caches. That means multi-instance and serverless deployments work out of the box: point every instance at the same DATABASE_URL and they converge automatically. See Key Concepts — Data in SQL and Portability.