---
url: /templates/nuxt-betterauth.md
description: >-
  Set up the Nuxt Better Auth template with PostgreSQL, email or Google sign-in,
  and DockIY deployments.
---

# Nuxt Better Auth template

[Source](https://codeberg.org/chris-paganon/dockiy-nuxt-betterauth) ·
[Demo](https://nuxt-betterauth.dockiy.com) ·
[Database-free Nuxt template](/templates/nuxt)

Nuxt 4, TypeScript, Tailwind/shadcn-vue, Better Auth, PostgreSQL/Drizzle, and
Brevo email, with separate staging and production deployments through DockIY.

## Local development

Install Node.js 24+, pnpm 11+, Docker Compose, and the [DockIY CLI](/guide/installation).
Encrypted files also need SOPS and your own [SOPS identity](/guide/installation#sops-identity).
Create a project with `dockiy app init nuxt-betterauth my-app`, or clone the template.

```bash
cp .env.example .env
openssl rand -base64 32
# Set NUXT_BETTER_AUTH_SECRET to the generated value and fill in NUXT_BREVO_*.
docker compose up -d
pnpm install --frozen-lockfile
pnpm db:migrate
pnpm dev
```

Nuxt and Drizzle load `.env`. Keep `NUXT_BETTER_AUTH_URL=http://localhost:3000`
locally. Email signup requires verification; configure the Brevo API key,
verified sender email, and sender name for signup and password reset.

### Encrypted local values

Configure your own `.sops.yaml`, then use `sops edit .enc.local.env` to create
an encrypted file with the same keys. Wrap commands manually:

```bash
sops exec-env .enc.local.env 'docker compose up -d'
sops exec-env .enc.local.env 'pnpm db:migrate'
sops exec-env .enc.local.env 'pnpm dev'
```

Or replace selected `package.json` scripts, for example:

```json
"dev": "sops exec-env .enc.local.env 'nuxt dev'",
"db:migrate": "sops exec-env .enc.local.env 'drizzle-kit migrate'"
```

Use the underlying command inside a script to avoid recursion. SOPS injects
variables without writing plaintext; restart Nuxt after changing them. Keep
plaintext env files/private keys uncommitted and replace inherited encrypted files.

## Staging and production

Start the DockIY base stack and [configure your VPS connection](/config/).
In `dockiy.yml`, set the app `name` and the staging/production `host` values.
Keep each environment's `secrets_file` pointing at its own encrypted dotenv.

```bash
sops edit .enc.staging.env
sops edit .enc.production.env
```

Use `.env.example` as the key list, with separate credentials and auth secrets:

| Setting | Staging example | Production example |
| --- | --- | --- |
| `POSTGRES_DB` / `POSTGRES_USER` | `my_app_staging` | `my_app` |
| `POSTGRES_PASSWORD` | Separate database password | Separate database password |
| `POSTGRES_DOCKER_PORT` | `5434` | `5435` |
| `NUXT_DATABASE_URL` | `postgresql://my_app_staging:PASSWORD@db:5432/my_app_staging` | `postgresql://my_app:PASSWORD@db:5432/my_app` |
| `NUXT_BETTER_AUTH_SECRET` | Fresh `openssl rand -base64 32` output | Fresh `openssl rand -base64 32` output |
| `NUXT_BETTER_AUTH_URL` | `https://staging.example.com` | `https://example.com` |
| `NUXT_BREVO_*` | API key and verified sender | API key and verified sender |
| `NUXT_GOOGLE_CLIENT_ID` / `NUXT_GOOGLE_CLIENT_SECRET` | Optional Google OAuth credentials | Optional Google OAuth credentials |

**In staging and production, the port in `NUXT_DATABASE_URL` is always `5432`**: it
is PostgreSQL's internal container port. `POSTGRES_DOCKER_PORT` exposes a separate
port on the VPS host so you can connect from your computer through an SSH tunnel.

The auth origin must match the environment host; it determines email links and
OAuth callbacks. DockIY decrypts the selected file into `deploy.env` and passes
its values to the app. New server settings need a `runtimeConfig` entry in
`nuxt.config.ts` and a `NUXT_*` variable; only browser-visible values go under `public`.

Commit the application, migrations, and encrypted configuration, then deploy:

```bash
dockiy app deploy staging
dockiy app deploy production --version v1.0.0
```

The migration image runs before the app starts. See [deployment commands](/guide/deploying-apps)
for status and rollback. Rollback restores the app, not the database schema.

## Database

Keep `NUXT_DATABASE_URL` credentials consistent with `POSTGRES_*`. URL-encode
special characters in URI credentials (`@` → `%40`); keep `POSTGRES_PASSWORD`
unencoded. Changing env values does not change credentials in an existing DB volume.

| Connection from | Address |
| --- | --- |
| Local Nuxt / Drizzle | `localhost:5432`, or your `POSTGRES_DOCKER_PORT` |
| App, migration, or pgAdmin container | `db:5432` |
| Desktop client via SSH tunnel | Your tunnel's local port |

The local example URL is
`postgresql://dockiy-nuxt-betterauth:secret-password@localhost:5432/dockiy-nuxt-betterauth`.
Changing `POSTGRES_DOCKER_PORT` changes only the host port; update the local URL
too. Choose distinct unused ports for deployed environments; they bind to VPS
loopback. To reach staging from a desktop client:

```bash
ssh -N -L 15432:127.0.0.1:5434 USER@VPS
# Connect to localhost:15432 with staging credentials.
```

Local pgAdmin is at `http://localhost:82` (`admin@m.com` / `admin`). Register
host `db`, port `5432`, with the local `POSTGRES_*` credentials.

Define/export tables in `server/db/schema/`; import `getDb` from `#server/db`
for queries. After schema edits, run `pnpm db:generate`, review/commit the SQL
and metadata, then `pnpm db:migrate` locally. Reserve `pnpm db:push` for disposable DBs.

## Authentication

Email/password and Google auth are preconfigured.

* **Client session:** import `authClient` from `@/lib/auth-client` and read the
  session with `await authClient.useSession(useFetch)`.
  [Session usage](https://better-auth.com/docs/integrations/nuxt#use-the-session).
* **Gate pages:** `definePageMeta({ middleware: "auth" })`.
  [Protect pages](https://better-auth.com/docs/integrations/nuxt#protect-pages).
* **Protect APIs separately:** call `const user = await requireUser(event)`,
  then check resource ownership.

### Google auth setup

1. Select/create a project in [Google Cloud Console](https://console.cloud.google.com/auth/overview). Under **Google Auth Platform**, complete **Branding** and **Audience**; choose **External** for users outside your organization and add test users while testing. [Consent setup](https://developers.google.com/workspace/guides/configure-oauth-consent).
2. Open **Clients → Create client → Web application**. Add the exact callback URLs below under **Authorized redirect URIs**. [Google OAuth setup](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred).
3. Copy the client ID/secret to `NUXT_GOOGLE_CLIENT_ID` and `NUXT_GOOGLE_CLIENT_SECRET` in each environment's dotenv file. Set `NUXT_BETTER_AUTH_URL` to the corresponding origin, then restart locally or redeploy. [Better Auth Google guide](https://better-auth.com/docs/authentication/google).

| Environment | Authorized redirect URI |
| --- | --- |
| Local | `http://localhost:3000/api/auth/callback/google` |
| Staging | `https://staging.example.com/api/auth/callback/google` |
| Production | `https://example.com/api/auth/callback/google` |

Use your actual domains. The callback URI must match exactly, including scheme
and path; a mismatch causes `redirect_uri_mismatch`. Before public launch,
review **Audience → Publishing status** and Google's requested verification steps.
