---
title: "Deployment"
description: "Deploy an Agent-Native app or workspace to a Nitro-compatible platform with a persistent PostgreSQL database."
search: "deployment deploy app workspace hosting database Nitro Node.js Docker Vercel Netlify Cloudflare"
---

# Deployment

Agent-Native apps run on [Nitro](https://nitro.build), so the same app can
deploy to Node.js, serverless functions, edge workers, or a managed host. Pick
the path that matches what you are shipping:

<Cards columns={2}>

### :server: [Deploy an app](/docs/deploy-an-app)

Build and launch one standalone app on Node.js, Docker, Vercel, Netlify, or
Cloudflare Workers.

### :network: [Deploy a workspace](/docs/workspace-deployment)

Build several apps behind one origin with shared login and same-origin app to
app communication.

</Cards>

## Agents that outlive their functions {#durable-agents}

Agent runs do not depend on the function that started them staying alive. Each
run is tracked in your database, so a turn that reaches a serverless time limit
continues in a fresh invocation, a reconnecting browser picks the reply back
up, and a function that dies mid-turn is recovered instead of leaving the chat
stuck. That is what lets the same agent run cheaply on serverless hosts, move
between providers without code changes, and share a deployment with the rest of
your app. See [Agents that survive any host](/docs/durable-background-runs#survive-any-host).

## Before you deploy: choose a persistent database {#persistent-database}

Every deployed app needs a persistent PostgreSQL database. Local development can use
the PGlite database at `data/pglite`, but container, preview, and serverless
filesystems can be reset at any time.

<Steps>

### Choose a provider

Use a hosted PostgreSQL provider such as [Neon](/docs/neon),
[Supabase](/docs/supabase), Railway, Render, [Amazon RDS for PostgreSQL](/docs/aws-rds),
[Cloud SQL for PostgreSQL](/docs/cloud-sql), or [Azure Database for PostgreSQL](/docs/azure-postgresql).
Use the [Plain Postgres](/docs/postgres) guide for other managed services and
self-hosted PostgreSQL servers. The framework configures PostgreSQL from the
connection URL.

### Set the connection string

Set `DATABASE_URL` in the hosting provider's environment-variable or secret
settings before the first production deploy. A local `.env` configures your
machine only and is not uploaded by the deploy command.

### Add production secrets

Generate a stable signing secret with `openssl rand -hex 32`, then set it as
`BETTER_AUTH_SECRET` for a standalone app. A workspace also needs `A2A_SECRET`
when it uses signed cross-app requests or background handoffs. The [deployment
environment reference](/docs/deployment-environment-variables) covers the rest
of the production configuration.

### Run migrations

Run `pnpm migrate:production` against the production database before the first
visit. It creates the tables the app needs. Run it again after every upgrade,
before the new version goes live. Each hosting guide shows where this step fits.

</Steps>

<Callout tone="info">

In a workspace, every app inherits the root `DATABASE_URL` by default. Set
`<APP_NAME>_DATABASE_URL` only when one app needs a different database.

</Callout>

## Email delivery is optional, but recommended {#email-provider}

An email provider is not required for email/password authentication. Without
one, a deployed app uses email/password signup and login, skips signup email
verification, and does not offer magic-link authentication.

Agent-Native does not bundle an email-delivery account. Connect Resend or
SendGrid before deploying if you want magic links, email
verification, password resets, invitations, or share notifications. Set
`RESEND_API_KEY` or `SENDGRID_API_KEY` in the deployment environment; SendGrid
also needs `EMAIL_FROM`. See [Authentication](/docs/authentication) for the
available authentication modes.

### Customize framework emails {#email-templates}

Every email the framework sends has a default template. To use your own
design, register an override from a server plugin. The framework still picks
the recipient and sender and delivers the message. Your override decides what
the message says and how it looks.

```ts
// server/plugins/email-templates.ts
import {
  escapeEmailHtml,
  overrideTransactionalEmail,
} from "@agent-native/core/server";

export default function registerEmailTemplates(): void {
  overrideTransactionalEmail("core.resource-shared", (props) => ({
    subject: `${props.sender.name} shared "${props.resource.title}"`,
    html: `<p>${escapeEmailHtml(props.sender.name)} invited you to
      <a href="${escapeEmailHtml(props.resource.url)}">${escapeEmailHtml(props.resource.title)}</a>
      in ${escapeEmailHtml(props.app.name)}.</p>`,
  }));
}
```

| Email id                         | Props                                                                              |
| -------------------------------- | ---------------------------------------------------------------------------------- |
| `core.verify-signup`             | `email`, `verifyUrl`                                                               |
| `core.reset-password`            | `email`, `resetUrl`                                                                |
| `core.magic-link`                | `email`, `magicLinkUrl`                                                            |
| `core.change-email-confirmation` | `email`, `newEmail`, `confirmationUrl`                                             |
| `core.change-email-verification` | `email`, `verifyUrl`                                                               |
| `core.organization-invite`       | `invitee`, `orgName`, `inviter`, `acceptUrl`                                       |
| `core.builder-credit-limit`      | `subject`, `heading`, `body`, `upgradeLabel`, `upgradeUrl`                         |
| `core.resource-shared`           | `recipientEmail`, `sender`, `resource` (`type`, `title`, `url`), `role`, `message` |

Every override also receives `app` (`name`, `logoUrl`). A second argument holds
the default rendering, so you can reuse its subject or text.

- Props are raw values. Escape them with `escapeEmailHtml` before putting them
  in an HTML string.
- Return `react` with a React element instead of `html` to render a component.
  React escapes props for you.
- Leave out `subject` to keep the default subject. Leave out `text` to derive
  the plain-text body from your HTML.
- If an override throws, the send fails. The framework does not fall back to
  the default design. The transactional email preview renders your override,
  so check it there.

## Supported deployment targets

Each target has a step-by-step guide. The app code and database stay the same
whichever target you choose. Only the build preset and the deploy command
change.

<!-- i18n-copy-ignore: renderer-only Diagram attributes keep this shared UI block in clean mode. -->

<Diagram id="doc-block-deployment-targets" title="Supported deployment targets" summary="Agent-Native apps can deploy to Node.js, Docker, Vercel, Netlify, and Cloudflare Workers." frame="hide" renderMode="design">

```html
<!-- i18n-copy-ignore: deployment target logo markup is visual-only; labels are unchanged. -->
<div class="deployment-target-grid">
  <a class="deployment-target" href="/docs/node-js">
    <span class="deployment-target-mark">
      <img
        class="deployment-target-logo"
        src="/integration-logos/nodejs.svg"
        alt=""
      />
    </span>
    <strong>Node.js</strong>
  </a>
  <a class="deployment-target" href="/docs/docker">
    <span class="deployment-target-mark">
      <img
        class="deployment-target-logo"
        src="/integration-logos/docker.svg"
        alt=""
      />
    </span>
    <strong>Docker</strong>
  </a>
  <a class="deployment-target" href="/docs/vercel">
    <span class="deployment-target-mark">
      <img
        class="deployment-target-logo deployment-target-logo-light"
        src="/integration-logos/vercel.svg"
        alt=""
      />
      <img
        class="deployment-target-logo deployment-target-logo-dark"
        src="/integration-logos/vercel-white.svg"
        alt=""
        aria-hidden="true"
      />
    </span>
    <strong>Vercel</strong>
  </a>
  <a class="deployment-target" href="/docs/netlify">
    <span class="deployment-target-mark">
      <!-- i18n-copy-ignore: Netlify's light/dark logo pair is image markup; localized deployment pages have no logo grid. -->
      <img
        class="deployment-target-logo deployment-target-logo-light"
        src="/integration-logos/netlify.svg"
        alt=""
      />
      <img
        class="deployment-target-logo deployment-target-logo-dark"
        src="/integration-logos/netlify-darkmode.svg"
        alt=""
        aria-hidden="true"
      />
    </span>
    <strong>Netlify</strong>
  </a>
  <a class="deployment-target" href="/docs/cloudflare#cloudflare-workers">
    <span class="deployment-target-mark">
      <img src="/integration-logos/cloudflare.svg" alt="" />
    </span>
    <strong>Cloudflare Workers</strong>
  </a>
</div>
```

```css
.deployment-target-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.deployment-target {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 58px;
  padding: 10px 12px;
  border: 1px solid hsl(var(--border));
  border-radius: 10px;
  color: inherit;
  text-decoration: none;
}
.deployment-target:hover {
  border-color: hsl(var(--primary));
}
.deployment-target-mark {
  display: grid;
  width: 30px;
  height: 30px;
  place-items: center;
  flex: 0 0 auto;
}
.deployment-target-logo {
  display: block;
  width: 26px;
  height: 26px;
  object-fit: contain;
}
.deployment-target-logo-dark {
  display: none;
}
[data-theme="dark"] .deployment-target-logo-light {
  display: none;
}
[data-theme="dark"] .deployment-target-logo-dark {
  display: block;
}
@media (max-width: 640px) {
  .deployment-target-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
```

</Diagram>

## Advanced deployment

Start with the path above. Use these pages when you need provider-specific
settings or production controls:

- [**Node.js**](/docs/node-js) - run the default Nitro server preset
- [**Docker**](/docs/docker) - package the Node.js server in a container
- [**Vercel**](/docs/vercel), [**Netlify**](/docs/netlify), and [**Cloudflare Workers**](/docs/cloudflare#cloudflare-workers) - configure hosted targets
- [**SSR Caching**](/docs/ssr-caching) - configure the public shell cache
- [**Deployment Environment Variables**](/docs/deployment-environment-variables) - production secrets, runtime flags, and workspace inheritance
- [**Production Agent Access**](/docs/actions-agent-tools) - control framework tools and code execution
- [**Updating UI in Production**](/docs/updating-ui-in-production) - understand the managed visual-editing workflow

<a id="self-host-quickstart"></a>
<a id="skip-ensure-tables"></a>

These anchors keep older links working. Self-hosting with a container is covered
in the [Docker guide](/docs/docker). Schema setup and cold-start guidance live
in the [deployment environment reference](/docs/deployment-environment-variables#skip-ensure-tables).

## What's next

- [**Deploy an app**](/docs/deploy-an-app)
- [**Deploy a workspace**](/docs/workspace-deployment)
- [**Database**](/docs/server-database#production)
- [**Security**](/docs/security)
- [**Authentication**](/docs/authentication)
