Manage production databases and content

Use baselines and incremental migrations correctly, synchronize documentation content, and keep recovery records for production data.

A production release includes Worker code, database schemas, and documentation content in KV. Each is updated separately. A successful Worker upload does not mean that all data is ready.

Initial deployment and subsequent updates already include database migrations and KV synchronization. Use the deployment scripts for normal releases. The standalone commands in this article are mainly for advance checks, troubleshooting, and resuming interrupted steps.

1. Confirm which database you are operating on

The template uses two D1 databases:

BindingBaseline fileTable used to identify the baselinePurpose
DBschema/db-init.sqlsaas_userMain business data
ANALYTICS_DBschema/analytics-init.sqlanalytics_sessionAnalytics data

The account and binding IDs in wrangler.jsonc determine the remote targets. Check both bindings before proceeding. Similar database names alone do not confirm that you have selected the right targets.

Local and remote commands are separate:

npm run db:migrate:local
npm run db:migrate:remote

These commands target different environments. You do not need to run both in sequence every time. During development, verify migrations locally first. For production releases, deploy:update usually handles remote migrations.

2. How the migration script handles existing data

The db:migrate command processes the main and analytics databases separately:

  1. If the database has no user tables, it runs the corresponding baseline file.
  2. If user tables exist and the table identifying the baseline is present, it keeps the existing tables and proceeds with incremental migrations.
  3. If the database is not empty but lacks the table identifying the baseline, it stops and asks you to investigate.
  4. It applies pending migrations for that database from schema/migrations.

The baseline identification table only indicates whether the database resembles an initialized project. It does not mean that the script has validated every field in the entire schema. For older or manually modified databases, review the actual structure before preparing migrations.

If an error mentions db:reset:remote, do not treat it as a routine fix. A reset rebuilds the schema. Do not use it to resolve migration issues in a production database that contains users, orders, or business records.

3. Write incremental migrations for business changes

For a complete example of adding a business table, see Build a user data API from a business table. Before deployment, check that migration files are committed alongside the code.

Place files in schema/migrations/. Filenames consist of a four-digit number, the target database, and a description, for example:

schema/migrations/
├── 0001-db-saved-links.sql
└── 0002-analytics-add-source-field.sql

These names only illustrate the convention. You do not need to create the second file. Use -db- for the main database and -analytics- for analytics. Use lowercase letters, numbers, and hyphens in the rest of the description.

Do not continue using the separate initialization commands from older documentation for files such as schema/init.sql, payment.sql, and event-execution.sql. The main database baseline is now maintained centrally in schema/db-init.sql.

Follow three principles when adding migrations:

  • Run them locally and verify business behavior first. Checking only that the SQL has no syntax errors is not enough.
  • Do not rewrite migrations that have already run. Put later corrections in newly numbered files to preserve a traceable history.
  • Do not put the same table creation statement in both the baseline and a migration. Otherwise, an empty database will attempt to create the table again when applying migrations after the baseline.

Use these read-only commands to inspect pending remote migrations:

npx wrangler d1 migrations list DB --remote
npx wrangler d1 migrations list ANALYTICS_DB --remote

4. Keep migrations compatible with the running version

The deploy:update command first verifies the code, then migrates the remote databases, and finally publishes the new Worker. The old Worker may still receive requests while migrations run.

For example, to rename a business field from title to name, do not delete title first and expect the new code to take over immediately in the same release. You can add the new field, migrate data, and switch reads and writes in stages, then remove the old field after confirming that the old code no longer uses it.

Even a simple new table requires considering the state left by a failed release: the database may already contain the table while the Worker still runs the old version. If the migration preserves the structures that the old code depends on, it is easier to fix the problem and continue the release.

The two D1 databases do not share a cross-database transaction. If the main database migration succeeds but the analytics migration fails, check how far each database progressed before addressing the failed step. Do not assume that the script automatically undoes changes to the first database.

5. Prepare recovery records before schema changes

Once you have real data, release records should include migration files, execution times, target databases, and available recovery points. Cloudflare D1 provides Time Travel, which lets you obtain database recovery bookmarks. See the official documentation for retention limits and restore procedures.

npx wrangler d1 time-travel info DB
npx wrangler d1 time-travel info ANALYTICS_DB

To save separate SQL exports, run:

npx wrangler d1 export DB --remote --output=./db-before-release.sql
npx wrangler d1 export ANALYTICS_DB --remote --output=./analytics-before-release.sql

Before running these commands, make sure the filenames will not overwrite older backups you need to keep. Exports may contain account and business data. Move them to a protected backup location, and do not commit them to Git or place them in public.

Restoring a database changes actual business state. Before restoring, identify which subsequent writes would be lost and whether orders, queued jobs, and the external payment platform need to be reconciled. Having an export file does not mean the restore process has been verified.

D1 backups do not include R2 files, KV content, or Durable Object state. Define retention and recovery procedures for those resources according to their purposes.

6. Synchronize documentation and blog content

After local articles are compiled, production content is synchronized to MAIN_KV. Deploying only the Worker can leave page code updated while article bodies, sidebars, or article lists still show old content.

The project currently provides these commands:

npm run gen:search-index
npm run kv:sync:local
npm run kv:sync:remote

The gen:search-index command compiles content collections and generates search indexes. It does not upload content to remote KV. The two synchronization commands target local and remote KV, respectively. Confirm which environment you intend to update before running either.

KV synchronization updates current content and removes old keys under the corresponding content prefixes when they are no longer in the local collection. Review the synchronization changes when deleting articles, changing paths, or removing languages. Synchronization is not always append-only.

Normally, use deploy:update to publish. It includes the build and remote KV synchronization. Running KV synchronization separately is appropriate when code has been published but content synchronization was interrupted. If article bodies, paths, or searchable content change, still confirm that the static search indexes and content belong to the same version.

For details on content publishing, see Documentation system and Blog.

7. Verify the results

After migration, use read-only queries to confirm that the baseline tables exist:

npx wrangler d1 execute DB --remote --command="SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'saas_user';"
npx wrangler d1 execute ANALYTICS_DB --remote --command="SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'analytics_session';"

The presence of a table only confirms that the basic structure is visible. Continue by testing the actual business flows affected by the release. For example, after adding the saved-links table, create and read a record rather than checking only the table name.

After synchronizing content, open the documentation entry page, an article, and search results on the production site. Confirm that the language, sidebar, and body are consistent. If the homepage works but documentation returns 404, first check content feature switches, the MAIN_KV binding, and synchronization output.

Next: Initial deployment and subsequent updates.