Skip to content

Build1 publisher2 min readPublished

Cloning the live Postgres volume runs the failing NOT NULL migration on a disposable instance

A Unikraft Cloud walkthrough clones a running database's persistent volume and boots a throwaway Postgres against it, so the NOT NULL migration that empty dev databases accept meets real rows first. The faster branching feature needs an enterprise license.

The Engineer · Build desk

Illustration accompanying Cloning the live Postgres volume runs the failing NOT NULL migration on a disposable instance

What happened

  • The guide builds a multi-tenant notes API on PostgreSQL on Unikraft Cloud, clones the live database's persistent volume, and promotes a migration fix only after it has succeeded against the cloned copy.
  • The statement under test is ALTER TABLE notes ADD COLUMN priority INT NOT NULL, which the post calls the migration that makes the whole exercise worth doing.
  • The walkthrough deliberately uses the broadly available path, cloning the persistent volume and booting a fresh Postgres against it, because that works on the hosted platform today with no special license.
  • The failing version of migration 00002 stays in the repository in a directory named broken/, while migrations/ ships the fixed 00002 that the deployment applies.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Unikraft's millisecond-pause branching sits behind an enterprise license, so hosted-plan users wait on a volume clone and a full Postgres boot before the rehearsal tells them anything.
  • cost On the pg_dump and restore path the post rejects, storage cost doubles for as long as the copy lives, so rehearsal gets more expensive exactly as the database grows big enough to be worth rehearsing.
  • decision A green pipeline stops being evidence that a destructive DDL change is safe, so the team has to decide which environment issues the go signal.
  • exposure The disposable instance holds a copy of live tenant rows, so it inherits whatever access rules the production database is held to.

Postgres wants a value behind the constraint before it will accept one. The moment `priority` becomes NOT NULL, every row already in `notes` needs a value, and the statement supplies no default, so there is nothing to backfill with [4]. An empty table has no rows to check, so the same SQL is accepted in a fresh local database and in most CI pipelines [5]. That fixture is the one table shape this change cannot fail against [23].

Unikraft Cloud has a faster path than the one the guide takes. The post says the `--branch` feature snapshots a running instance's memory and volume state, is copy-on-write, and pauses the source for only a few milliseconds [6]. The post states those properties of branching, not of the volume clone the walkthrough actually uses, and it does not state how long a clone takes or how much space it needs [10].

Adoption cost is a toolchain list: Go 1.25 or later, Docker with BuildKit enabled, the Unikraft CLI with `unikraft login` completed, a Unikraft Cloud account, and `psql` installed locally [17]. `go get` resolved pgx to v5.10.0 and goose to v3.27.2 [14]. The connection pool is thin, a single function that reads `DATABASE_URL` and hands back a `pgxpool.Pool`, returning `ErrNoDSN` when the variable is unset and expecting TLS settings in the DSN itself [19]. During the rehearsal the live instance keeps serving while a second Postgres runs against the clone [21].

Goose was chosen because it keeps migrations as individual, ordered SQL files [13]. `cmd/migrate` applies the embedded set from `migrations/` [11]. The set it can apply is whatever sits in that directory [22].

The detail I would steal from this repository is in `00001_init.sql`. `gen_random_uuid()` is built into PostgreSQL 13 and later, so there is no `pgcrypto` extension to enable [15]. The custom Postgres image used in this deployment does not ship `pgcrypto` at all, so a `CREATE EXTENSION pgcrypto` line carried over out of habit from an older project would break the very first migration [16].

What to watch

  • Whether Unikraft moves instance branching off the enterprise-license gate and onto the hosted plan.
  • Published clone and boot times for large volumes, which would show whether the rehearsal stays quick as the database grows.
  • Migrations that succeed on a clone but lock the table in production, where a clone reports no failure to catch.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories