---
title: "Deployment"
description: "Deploy the Nitro API as a Cloudflare Worker and the Vue app as static files. Marked where Cloudflare UI steps are inferred."
canonical_url: "https://vinicunca.dev/taman/guides/deployment"
---
# Deployment

> Deploy the Nitro API as a Cloudflare Worker and the Vue app as static files. Marked where Cloudflare UI steps are inferred.

The API is a **Cloudflare Worker** (`cloudflare-module`). The web app is a **static Vite build**. There is no first-party “one click” deploy script that ships both.

> [!NOTE]
> 
> `scripts/deploy/Dockerfile` and `.gitpod.yml` are **stale** (nginx + `playground/dist`, `pnpm run dev:play`, port 5555). `create-taman` **deletes** `scripts/deploy/**` and `.gitpod.yml` from generated projects. Do not follow them.

## 1. Build the API

```bash
pnpm nx run api:build
```

Nitro (`cloudflare.deployConfig: true`) emits Worker assets and wrangler config. Worker name in the source repo is `taman-better-auth-back`. Generated projects rename it to `<name>-api`. **Renaming a deployed worker creates a new worker** — keep the name stable after first deploy.

Then, from the API output directory Nitro documents for your Nitro version:

```bash
npx wrangler deploy
```

> [!WARNING]
> 
> The exact wrangler cwd/output folder is whatever Nitro 3.0.260903-beta emits (`deployConfig`). Confirm against the build log; this page does not guess a path that drifts with Nitro betas.

## 2. Cloudflare resources

Create (names from `nitro.config.ts`, rewritten on generate):

| Resource | Source name | Generated |
| --- | --- | --- |
| Durable Object class | `TodoPublisherObject` | unchanged |
| DO binding | `TODO_PUBLISHER` | unchanged |
| Queue | `taman-email` | `<name>-email` |
| DLQ | `taman-email-dlq` | `<name>-email-dlq` |
| `send_email` binding | `EMAIL` | unchanged |

SQLite DO migration tag: `v1` / `new_sqlite_classes: ['TodoPublisherObject']`. Applying this twice on an existing worker can fail — treat it as one-time.

**Unverified in-repo:** dashboard steps to attach Hyperdrive / TCP Postgres, Email Routing destination, and queue production settings. Use Cloudflare docs for those products.

## 3. Secrets and vars

The API reads these values only through `useRuntimeConfig()`, and Nitro fills `runtimeConfig` from `NITRO_*` variables. Set the Worker secrets / vars with the same names as `apps/api/.env`:

- `NITRO_DATABASE_URL`
- `NITRO_TRUSTED_ORIGINS` — your production web origin(s), comma-separated
- `NITRO_BASE_URL` — public API URL
- `NITRO_APP_URL` — public web URL
- `NITRO_BETTER_AUTH_SECRET`
- `NITRO_GOOGLE_CLIENT_ID` / `NITRO_GOOGLE_CLIENT_SECRET` (optional)
- `NITRO_EMAIL_FROM` / `NITRO_EMAIL_FROM_NAME`

An unprefixed name such as `DATABASE_URL` is ignored by the Worker and leaves the value empty. After the first deploy, check that `/readyz` returns OK.

Google Cloud Console: authorized redirect `{NITRO_BASE_URL}/api/auth/callback/google`.

## 4. Database

Do **not** use `drizzle-kit push` in production. Generate migrations (`pnpm db:generate`), commit them, run `pnpm db-pg:migrate:prod` from a trusted network (or CI). drizzle-kit reads the production URL from an unprefixed `DATABASE_URL`, unlike the Worker.

Postgres must be reachable from the Worker (Hyperdrive, Cloudflare Tunnel, or a publicly reachable instance with a tight allow-list). **How you expose Postgres is not specified in the template.**

## 5. Web static files

```bash
pnpm --filter @taman/web build
```

Set `VITE_API_URL` to the Worker URL, `VITE_ROUTER_HISTORY` (`hash` is the production example — useful if you cannot configure SPA fallbacks), `VITE_BASE` if not serving at `/`.

Host `apps/web/dist` on Cloudflare Workers Static Assets, Pages, or any static host. Configure HTTPS, and put that origin in `NITRO_TRUSTED_ORIGINS`.

Production builds may emit `_app-config-[version]-[hash].js` (`window._TAMAN_ADMIN_DEV_CONFIG_`) so `VITE_GLOB_*` can change without rebuilding — see [vite-config](https://vinicunca.dev/taman/packages/tooling).

## 6. Cookies and CORS

better-auth cookies use prefix `taman`. The SPA must call the API with `credentials: 'include'`. `NITRO_TRUSTED_ORIGINS` must list exact origins (`https://app.example.com`, not `*`). Empty config yields **no** allowed origins (`resolveTrustedOrigins` returns `[]`).


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
