---
# quotesdb-4tec
title: '[TRIAGE] D1 migrations in OpenTofu — null_resource local-exec vs separate wrangler step vs manual'
status: completed
type: task
priority: critical
created_at: 2026-03-10T23:32:07Z
updated_at: 2026-03-10T23:32:07Z
---
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
D1 migrations in OpenTofu: how do we apply the SQL schema to a newly created D1 database? Options are a null_resource local-exec in OpenTofu, a separate wrangler d1 execute step, or a manual migration step.
1. **null_resource local-exec** — run `wrangler d1 execute` as a provisioner in OpenTofu. Ties infra and schema together in one `tofu apply`.
2. **Separate wrangler step** — document as a manual step after `tofu apply`. Simpler OpenTofu config, slightly more manual.
3. **API startup migration** — the API runs `CREATE TABLE IF NOT EXISTS` on startup. Works but risks schema drift in production.
**Option 2: Separate wrangler step.**
- **null_resource local-exec rejected:** `null_resource` provisioners are an OpenTofu anti-pattern. They don't re-run unless tainted, aren't tracked in state, require wrangler installed on the CI runner at `tofu apply` time, and break idempotency. The convenience of a single command is not worth the coupling.
- **API startup migration rejected:** Cloudflare Workers spin up per-request via V8 isolates. There is no persistent startup phase. Running DDL (`CREATE TABLE IF NOT EXISTS`) before every request adds latency and is fragile. The Workers fetch handler (D1Repository, wasm32 path) does NOT run migrations. This is only viable for the native/local dev path (rusqlite), where `NativeRepository::run_migrations()` is called once at `main()` startup.
- **Separate wrangler step chosen:** This is Cloudflare's canonical approach. The schema SQL lives at `infra/schema.sql` (ticket bb1514). After `tofu apply`, run once:
```sh
wrangler d1 execute quotesdb --file infra/schema.sql --remote
```
Idempotent with `CREATE TABLE IF NOT EXISTS`. Integrates cleanly into CI/CD as a post-apply step. Keeps OpenTofu focused on infrastructure, not data.
**Note:** TRIAGE 580e66 asks the same question from the Workers runtime angle and arrives at the same answer. Both are now resolved together.
- Ticket d0da0b updated: constraint clarified (wrangler step, no null_resource).
- Ticket a5049d updated: migration strategy constraint updated (580e66 also resolved).
- Ticket 75489a updated: dependency on bb1514 added; goal updated to reference infra/schema.sql.
- Ticket 580e66 resolved as co-decided.
- New ticket bb1514 created: full implementation plan for `infra/schema.sql`.
`chore(quotesdb): resolve triage — d1-migrations-in-opentofu-nullresource-localexec-vs-separate`