CI/CD Deployment
NuxtHub applications deploy through standard CI/CD pipelines. Database migrations require special attention, particularly for Cloudflare D1.
Cloudflare Deployment
Workers Builds
The recommended approach is to use Cloudflare Workers Builds with explicit build, deploy, and preview deploy scripts. This keeps preview and production deploys aligned while applying D1 migrations before each deployment.
- Create a Workers project in the Cloudflare dashboard
- Connect your GitHub or GitLab repository
- Add the following scripts to your
package.json:
{
"scripts": {
"build": "bash -lc 'if [[ -z \"${WORKERS_CI_BRANCH:-}\" || \"$WORKERS_CI_BRANCH\" == \"main\" ]]; then pnpm run build:production; else pnpm run build:preview; fi'",
"build:production": "NODE_OPTIONS=--max-old-space-size=4096 nuxt build",
"build:preview": "NODE_OPTIONS=--max-old-space-size=4096 nuxt build --envName preview",
"deploy:cloudflare": "sh -c 'pnpm exec wrangler --cwd .output/server d1 migrations apply DB --remote && pnpm exec wrangler --cwd .output deploy --keep-vars'",
"deploy:cloudflare:preview": "sh -c 'pnpm exec wrangler --cwd .output/server d1 migrations apply DB --remote && pnpm exec wrangler --cwd .output versions upload'"
}
}
- Configure the Workers Builds settings:
- Build command:
pnpm build - Deploy command:
pnpm run deploy:cloudflare - Version command:
pnpm run deploy:cloudflare:preview - Root directory:
.
- Build command:
nuxt.config.ts during the build process. No manual wrangler.jsonc file is required.wrangler.jsonc with named environments, set CLOUDFLARE_ENV as a build-time variable for non-production environments in Workers Builds → Build configuration → Environment variables:CLOUDFLARE_ENV=previewfor preview deploymentsCLOUDFLARE_ENV=stagingfor staging deployments- Leave unset for production (uses default environment)
wrangler deploy and Workers Builds do not apply D1 migrations automatically.GitHub Actions
For more control over your deployment process, you can use GitHub Actions:
name: Deploy to Cloudflare
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 11
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
env:
CLOUDFLARE_ENV: ${{ github.ref == 'refs/heads/main' && '' || 'preview' }}
- name: Deploy
run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
D1 Migrations in CI/CD
The Problem
Unlike PostgreSQL or MySQL, where build-time migrations work because the database is reachable over the network, Cloudflare D1 presents unique challenges:
- It requires Worker bindings to access the database
- These bindings are only available at runtime
- CI build environments have no database connection
Solution: Migration-Aware Deploy Commands
Run migrations before deployment with Wrangler.
NuxtHub generates migrations_table and migrations_dir in .output/server/wrangler.json for D1 projects. This enables Wrangler-managed migrations, but it does not execute them automatically during wrangler deploy. Your deploy and preview deploy commands should run wrangler d1 migrations apply first, then upload or deploy the Worker.
_hub_migrations may be missing the .sql suffix. Wrangler requires .sql in order to recognize the migration files.Fix: Update the migration names in the _hub_migrations table to include the .sql suffix.
-- Preview affected rows
SELECT id, name FROM _hub_migrations WHERE name NOT LIKE '%.sql';
-- Append .sql
UPDATE _hub_migrations SET name = name || '.sql' WHERE name NOT LIKE '%.sql';
1. Disable Build-Time Migrations
export default defineNuxtConfig({
hub: {
db: {
dialect: 'sqlite',
driver: 'd1',
connection: { databaseId: '<database-id>' },
applyMigrationsDuringBuild: false
}
}
})
2. Add the Migration Step to Your Deployment Commands
Option A: Using Wrangler (recommended)
Use Wrangler's built-in D1 migrations command. This requires your migrations to follow Wrangler's migration format.
NuxtHub already generates the required metadata in .output/server/wrangler.json, so you can run migrations directly against the generated config. The following pattern works well with Workers Builds:
{
"scripts": {
"deploy:cloudflare": "sh -c 'pnpm exec wrangler --cwd .output/server d1 migrations apply DB --remote && pnpm exec wrangler --cwd .output deploy --keep-vars'",
"deploy:cloudflare:preview": "sh -c 'pnpm exec wrangler --cwd .output/server d1 migrations apply DB --remote && pnpm exec wrangler --cwd .output versions upload'"
}
}
This sequence does three things:
- It uses the generated
.output/server/wrangler.jsonconfiguration, includingmigrations_tableandmigrations_dir - It applies pending D1 migrations before deployment
- It runs the correct Cloudflare command for production traffic or preview versions
After a successful deploy, verify that no migrations remain:
npx wrangler d1 migrations list DB --remote --config .output/server/wrangler.json
Wrangler should report that there are no migrations to apply.
If you prefer to keep the commands in your CI workflow instead of package.json, use the same sequence directly:
npx wrangler d1 migrations apply DB --remote --config .output/server/wrangler.json && npx wrangler deploy
If you maintain a manual wrangler.jsonc, configure it to use NuxtHub's migration table and output directory:
{
"d1_databases": [{
"binding": "DB",
"database_id": "<database-id>",
"migrations_table": "_hub_migrations",
"migrations_dir": ".output/server/db/migrations/sqlite/"
}]
}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 11
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Apply D1 Migrations
run: npx wrangler d1 migrations apply DB --remote --config .output/server/wrangler.json
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Deploy
run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Option B: Using nuxt db migrate
If you have the D1 HTTP credentials configured, you can use the Nuxt CLI to apply migrations:
- name: Apply D1 Migrations
run: npx nuxt db migrate
env:
NUXT_HUB_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
NUXT_HUB_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
NUXT_HUB_CLOUDFLARE_DATABASE_ID: ${{ secrets.CLOUDFLARE_DATABASE_ID }}
nuxt.config.ts has driver: 'd1' or driver: 'd1-http' configured.PostgreSQL / MySQL CI/CD
PostgreSQL and MySQL migrations can run at build time because the database is reachable via a connection string.
- name: Build with migrations
run: pnpm build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
nuxt build.Running Migrations in a Dedicated Job
For more control over the migration process, you can run migrations in a separate job:
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
- run: pnpm install
- name: Run migrations
run: npx nuxt db migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
deploy:
needs: migrate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... build and deploy steps
Vercel Deployment
Vercel handles CI/CD automatically when you connect your repository.
Setup
- Import your project in the Vercel dashboard
- Configure environment variables under Project Settings → Environment Variables
- Vercel will deploy automatically on every push
Database Migrations
For Vercel deployments with PostgreSQL or MySQL, migrations run during the build process because Vercel's build environment has network access:
export default defineNuxtConfig({
hub: {
db: {
dialect: 'postgresql',
// Migrations run during build since Vercel's build environment has network access
}
}
})
Environment-Specific Secrets
GitHub Actions
Store your secrets in Repository Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN | Cloudflare API token with Workers, D1, KV, and R2 permissions |
CLOUDFLARE_ACCOUNT_ID | Your Cloudflare account ID |
CLOUDFLARE_DATABASE_ID | The D1 database ID |
DATABASE_URL | PostgreSQL or MySQL connection string |
Per-Environment Secrets
Use GitHub Environments to separate production and staging secrets:
jobs:
deploy-production:
environment: production
# This job uses secrets from the production environment
deploy-preview:
environment: preview
# This job uses secrets from the preview environment