From 915fc6e0f469adfd67bceb6cc37616eeda50bef8 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 3 Sep 2026 13:20:38 -0600 Subject: [PATCH 1/7] initial blog outline for snapshot blog --- src/content/blog/ddev-snapshots.md | 107 +++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/content/blog/ddev-snapshots.md diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md new file mode 100644 index 00000000..177996fc --- /dev/null +++ b/src/content/blog/ddev-snapshots.md @@ -0,0 +1,107 @@ +--- +title: "DDEV Snapshots: Checkpoints, Restores, and Seeded Databases" +pubDate: 2026-09-03 +summary: How DDEV database snapshots work, how to use them as checkpoints during migrations, and how to seed new projects or containers from a snapshot instead of a full import. +author: Randy Fay +categories: + - Guides + - Videos +--- + + + + + +## Table of Contents + +## What Snapshots Are + +A DDEV snapshot is a physical, "hot" backup of your database — `mariabackup`/`xtrabackup` for MySQL and MariaDB, or `pg_basebackup` for Postgres — not a `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. + +Snapshots live in `.ddev/db_snapshots/`, and the filename encodes the database type and version, for example `mariadb_11.8`. That's why a snapshot only restores cleanly against a matching engine and version — restoring a `mariadb_11.8` snapshot into a `mariadb_10.11` project will fail unless you pass `--force`. + +Snapshots are compressed by default. `--uncompressed` skips the decompression step on restore, trading a much larger file on disk for a faster restore. Postgres doesn't support uncompressed snapshots. + +## Core Commands + +- `ddev snapshot --name=` — create a snapshot +- `ddev snapshot restore ` — restore a snapshot; an interactive TUI picker appears if you omit the name +- `ddev snapshot restore --latest` — restore the most recent snapshot +- `ddev snapshot restore $HOME/tmp/mysnapshot-mariadb_11.8.zst` — restore from an arbitrary path, not just `.ddev/db_snapshots/` +- `ddev snapshot --list` (`-l`) — table of snapshot name, created date, size, database version, and compression; shows a Worktree column when relevant +- `ddev snapshot --cleanup` (`-C`) — delete one snapshot (`--name=`) or all of them (prompts for confirmation unless `-y`) +- `ddev snapshot --all` (`-a`) — snapshot every project at once + +If your project has multiple Git worktrees, snapshots taken from other worktrees of the same repository are available too — by name, with `--latest`, or through the interactive list. + +## Snapshots as Migration Checkpoints + +Take a snapshot before each step of a migration or update: `ddev snapshot --name=pre-migration-step3`. If a step breaks something, restore the last good snapshot instead of re-importing the database from scratch. + +`ddev snapshot --list` becomes a log of checkpoints, and `restore ` or `restore --latest` jumps back to any of them instantly. + +This builds on the workflow described in [DDEV Database Management](ddev-local-database-management.md): snapshot, `ddev delete -O`, `ddev start`, restore. It's also a natural lead-in to seeding a new database volume directly from a snapshot, covered next. + +## Seeding New Projects with `--seed-snapshot` + +`ddev start` and `ddev restart` accept `--seed-snapshot=`, which seeds a **brand-new** database volume from a snapshot instead of the stock starter database. This only applies when there's no existing database — DDEV errors otherwise, telling you to add `--reset-database` or use `ddev snapshot restore`. + +`` can be a short name from `.ddev/db_snapshots/` or a full path: + +```bash +ddev start --seed-snapshot=$HOME/tmp/mysnapshot-mariadb_11.8.zst +``` + +This works for every database type DDEV supports, unlike the baked-dbimage technique below, which is MariaDB/MySQL-only — it's restored the same way `ddev snapshot restore` does, just at volume-creation time. + +There's a reserved snapshot name, **`seed`**: a snapshot literally named `seed` (created with `ddev snapshot --name=seed`) is picked up automatically on any brand-new volume, no flag needed. It becomes the project's default starting state. + +Combine `--seed-snapshot` with `--reset-database` to reseed an _existing_ project in one step: + +```bash +ddev restart --reset-database --seed-snapshot= -Oy +``` + +`-O` skips the automatic snapshot of the database being thrown away, and `-y` skips the confirmation prompt. + +This is the lightweight alternative to baking a seeded database image: no custom image or registry, just a snapshot file — good for local or small-team use where a shared registry is overkill. + +## Seed Snapshots + `--reset-database` + +Once you have a `seed` snapshot, `ddev restart --reset-database -Oy` repeatedly returns the project to that known-good state — handy between test runs. + +## Building a Seeded Database Image + +For teams that want to share a ready-to-go database via a container registry instead of a snapshot file, [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) builds a real multi-arch (linux/AMD64, linux/ARM64) image with a snapshot baked in: + +```bash +build-and-push-seeded-image.sh --snapshot=uncompressed-2g \ + --output-image=randyfay/uncompressed-2g:v1.25.4 --push \ + --base-image=ddev/ddev-dbserver-mariadb-11.8:v1.25.4 +``` + +This technique relies on `mariabackup`/`xtrabackup`, so it doesn't support Postgres. + +Uncompressed seeds make for a much larger image and a slower push, but a faster, decompress-free container startup. It's worth comparing the actual image sizes to see the bandwidth cost of each trade-off. + +## Using a Seeded Image via `dbimage:` + +Point a project at the seeded image in `.ddev/config.yaml` (or `.ddev/config.local.yaml`): + +```yaml +dbimage: randyfay/uncompressed-2g:v1.25.4 +``` + +Then: + +```bash +ddev stop && docker volume rm -mariadb && ddev start +``` + +Don't skip the volume-removal step — without it, DDEV keeps the existing database volume instead of pulling and using the seeded image. From 35486fc26c85b2b641a39396341860c950068303 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 4 Sep 2026 09:23:11 -0600 Subject: [PATCH 2/7] Add examples --- src/content/blog/ddev-snapshots.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index 177996fc..f92bd48b 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -1,6 +1,6 @@ --- title: "DDEV Snapshots: Checkpoints, Restores, and Seeded Databases" -pubDate: 2026-09-03 +pubDate: 2026-09-12 summary: How DDEV database snapshots work, how to use them as checkpoints during migrations, and how to seed new projects or containers from a snapshot instead of a full import. author: Randy Fay categories: @@ -105,3 +105,10 @@ ddev stop && docker volume rm -mariadb && ddev start ``` Don't skip the volume-removal step — without it, DDEV keeps the existing database volume instead of pulling and using the seeded image. + + +## Examples and resources + +* List of example images with seeds built in +* `build-and-push-seeded-image.sh --snapshot=seed --output-image=randyfay/d11_normal:v1.25.4 --push --base-image=ddev/ddev-dbserver-mariadb-11.8:v1.25.4` + From 4904cdc42d875af4bc0a93b1868c9886854bac7c Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 4 Sep 2026 10:25:17 -0600 Subject: [PATCH 3/7] edits --- src/content/blog/ddev-snapshots.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index f92bd48b..5d7b4518 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -20,20 +20,24 @@ categories: ## Table of Contents + + + ## What Snapshots Are -A DDEV snapshot is a physical, "hot" backup of your database — `mariabackup`/`xtrabackup` for MySQL and MariaDB, or `pg_basebackup` for Postgres — not a `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. +A DDEV snapshot is a physical, "hot" backup of your database — `maria-backup`/`xtrabackup` for MySQL and MariaDB, or `pg_basebackup` for Postgres — not a `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. Snapshots live in `.ddev/db_snapshots/`, and the filename encodes the database type and version, for example `mariadb_11.8`. That's why a snapshot only restores cleanly against a matching engine and version — restoring a `mariadb_11.8` snapshot into a `mariadb_10.11` project will fail unless you pass `--force`. -Snapshots are compressed by default. `--uncompressed` skips the decompression step on restore, trading a much larger file on disk for a faster restore. Postgres doesn't support uncompressed snapshots. +Snapshots are compressed with `zstd` by default. `--uncompressed` skips the decompression step on restore, trading a much larger file on disk for a faster restore. Postgres doesn't support uncompressed snapshots. ## Core Commands - `ddev snapshot --name=` — create a snapshot -- `ddev snapshot restore ` — restore a snapshot; an interactive TUI picker appears if you omit the name +- `ddev snapshot restore` - opens a TUI allowing you to select snapshot to restore +- `ddev snapshot restore ` — restore a named snapshot - `ddev snapshot restore --latest` — restore the most recent snapshot -- `ddev snapshot restore $HOME/tmp/mysnapshot-mariadb_11.8.zst` — restore from an arbitrary path, not just `.ddev/db_snapshots/` +- `ddev snapshot restore $HOME/tmp/mysnapshot-mariadb_11.8.zst` — restore from an arbitrary path, not from the default `.ddev/db_snapshots/` - `ddev snapshot --list` (`-l`) — table of snapshot name, created date, size, database version, and compression; shows a Worktree column when relevant - `ddev snapshot --cleanup` (`-C`) — delete one snapshot (`--name=`) or all of them (prompts for confirmation unless `-y`) - `ddev snapshot --all` (`-a`) — snapshot every project at once @@ -42,11 +46,11 @@ If your project has multiple Git worktrees, snapshots taken from other worktrees ## Snapshots as Migration Checkpoints -Take a snapshot before each step of a migration or update: `ddev snapshot --name=pre-migration-step3`. If a step breaks something, restore the last good snapshot instead of re-importing the database from scratch. +Take a snapshot before each step of a migration or update: `ddev snapshot --name=pre-migration-step3`. If a step breaks something, restore the last good snapshot instead of restarting the migration from scratch. (`ddev snapshot restore --latest` can be a great technique if you do this religiously.) `ddev snapshot --list` becomes a log of checkpoints, and `restore ` or `restore --latest` jumps back to any of them instantly. -This builds on the workflow described in [DDEV Database Management](ddev-local-database-management.md): snapshot, `ddev delete -O`, `ddev start`, restore. It's also a natural lead-in to seeding a new database volume directly from a snapshot, covered next. +This builds on the workflow described in [DDEV Database Management](ddev-local-database-management.md): snapshot, `ddev restart --reset-database`, restore. It's also a natural lead-in to seeding a new database volume directly from a snapshot, covered next. ## Seeding New Projects with `--seed-snapshot` From ca666aa709b921eff621d61e85cb30ca59c47c43 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Tue, 15 Sep 2026 09:15:55 -0600 Subject: [PATCH 4/7] Edits --- src/content/blog/ddev-snapshots.md | 41 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index 5d7b4518..b40dc9bc 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -18,16 +18,18 @@ categories: --> + + ## Table of Contents - -## What Snapshots Are + +## Snapshots are easy! -A DDEV snapshot is a physical, "hot" backup of your database — `maria-backup`/`xtrabackup` for MySQL and MariaDB, or `pg_basebackup` for Postgres — not a `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. +A DDEV snapshot is a physical, "hot" backup of your database — `mariadb-backup`/`xtrabackup` for MariaDB and MySQL, or `pg_basebackup` for Postgres — not a text-based `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. -Snapshots live in `.ddev/db_snapshots/`, and the filename encodes the database type and version, for example `mariadb_11.8`. That's why a snapshot only restores cleanly against a matching engine and version — restoring a `mariadb_11.8` snapshot into a `mariadb_10.11` project will fail unless you pass `--force`. +Normally snapshots live in `.ddev/db_snapshots/`, and the filename encodes the database type and version, for example `mariadb_11.8`. That's why a snapshot only restores against a matching engine and version — restoring a `mariadb_11.8` snapshot into a `mariadb_10.11` project will fail unless you pass `--force`. Snapshots are compressed with `zstd` by default. `--uncompressed` skips the decompression step on restore, trading a much larger file on disk for a faster restore. Postgres doesn't support uncompressed snapshots. @@ -40,7 +42,7 @@ Snapshots are compressed with `zstd` by default. `--uncompressed` skips the deco - `ddev snapshot restore $HOME/tmp/mysnapshot-mariadb_11.8.zst` — restore from an arbitrary path, not from the default `.ddev/db_snapshots/` - `ddev snapshot --list` (`-l`) — table of snapshot name, created date, size, database version, and compression; shows a Worktree column when relevant - `ddev snapshot --cleanup` (`-C`) — delete one snapshot (`--name=`) or all of them (prompts for confirmation unless `-y`) -- `ddev snapshot --all` (`-a`) — snapshot every project at once +- `ddev snapshot --all` (`-a`) — snapshot all projects (automatically starts stopped projects to accomplish this) If your project has multiple Git worktrees, snapshots taken from other worktrees of the same repository are available too — by name, with `--latest`, or through the interactive list. @@ -48,13 +50,13 @@ If your project has multiple Git worktrees, snapshots taken from other worktrees Take a snapshot before each step of a migration or update: `ddev snapshot --name=pre-migration-step3`. If a step breaks something, restore the last good snapshot instead of restarting the migration from scratch. (`ddev snapshot restore --latest` can be a great technique if you do this religiously.) -`ddev snapshot --list` becomes a log of checkpoints, and `restore ` or `restore --latest` jumps back to any of them instantly. +`ddev snapshot --list` becomes a log of checkpoints, and `ddev snapshot restore ` or `restore --latest` jumps back to any of them instantly. This builds on the workflow described in [DDEV Database Management](ddev-local-database-management.md): snapshot, `ddev restart --reset-database`, restore. It's also a natural lead-in to seeding a new database volume directly from a snapshot, covered next. ## Seeding New Projects with `--seed-snapshot` -`ddev start` and `ddev restart` accept `--seed-snapshot=`, which seeds a **brand-new** database volume from a snapshot instead of the stock starter database. This only applies when there's no existing database — DDEV errors otherwise, telling you to add `--reset-database` or use `ddev snapshot restore`. +DDEV's automatic starter database, or "seed" database, is normally built into the DB image. However, you can easily use a "seed" with more in it. For example, if you want to start a project with an alternate seed snapshot, `ddev start` and `ddev restart` accept `--seed-snapshot=`, which seeds the database volume from a snapshot instead of the stock seed database. This only applies when there's no existing database — DDEV errors otherwise, telling you to add `--reset-database` or use `ddev snapshot restore`. `` can be a short name from `.ddev/db_snapshots/` or a full path: @@ -76,13 +78,22 @@ ddev restart --reset-database --seed-snapshot= -Oy This is the lightweight alternative to baking a seeded database image: no custom image or registry, just a snapshot file — good for local or small-team use where a shared registry is overkill. +If you're working on a project that can always start with a seeded database, you can actually check in the seed and it will always be used by default on an empty project. + +```bash +git add -f .ddev/db_snapshots/seed-* +git commit -m "Add default seed db for clean startup" +``` + ## Seed Snapshots + `--reset-database` Once you have a `seed` snapshot, `ddev restart --reset-database -Oy` repeatedly returns the project to that known-good state — handy between test runs. ## Building a Seeded Database Image -For teams that want to share a ready-to-go database via a container registry instead of a snapshot file, [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) builds a real multi-arch (linux/AMD64, linux/ARM64) image with a snapshot baked in: +You can also create a replacement database image that has an alternate seed database built into it. This is especially great for delivering huge databases, as the process can be handled by the image, or an upstream process. + +For teams that want to share a ready-to-go database via a container registry instead of a snapshot file, [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) is an example that a real multi-arch (linux/AMD64, linux/ARM64) image with a snapshot baked in: ```bash build-and-push-seeded-image.sh --snapshot=uncompressed-2g \ @@ -99,20 +110,22 @@ Uncompressed seeds make for a much larger image and a slower push, but a faster, Point a project at the seeded image in `.ddev/config.yaml` (or `.ddev/config.local.yaml`): ```yaml +# .ddev/config.yaml or .ddev/config.db.yaml or .ddev/config.local.yaml dbimage: randyfay/uncompressed-2g:v1.25.4 ``` Then: ```bash -ddev stop && docker volume rm -mariadb && ddev start +ddev restart --reset-database --omit-snapshot -y ``` -Don't skip the volume-removal step — without it, DDEV keeps the existing database volume instead of pulling and using the seeded image. - - ## Examples and resources -* List of example images with seeds built in -* `build-and-push-seeded-image.sh --snapshot=seed --output-image=randyfay/d11_normal:v1.25.4 --push --base-image=ddev/ddev-dbserver-mariadb-11.8:v1.25.4` +- Some example images with seeds built into them + - uncompressed 2GB databases: + - compressed 2GB databases: + - MySQL 9.7 databases: +- Example image builder [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) +- Example invocation: `build-and-push-seeded-image.sh --snapshot=seed --output-image=randyfay/d11_normal:v1.25.4 --push --base-image=ddev/ddev-dbserver-mariadb-11.8:v1.25.4` From 5603328b6301d0429c95f05aa294f6aba55be193 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Tue, 15 Sep 2026 09:18:27 -0600 Subject: [PATCH 5/7] Linting --- src/content/blog/ddev-snapshots.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index b40dc9bc..a53bb43d 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -25,6 +25,7 @@ categories: + ## Snapshots are easy! A DDEV snapshot is a physical, "hot" backup of your database — `mariadb-backup`/`xtrabackup` for MariaDB and MySQL, or `pg_basebackup` for Postgres — not a text-based `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases. @@ -78,7 +79,7 @@ ddev restart --reset-database --seed-snapshot= -Oy This is the lightweight alternative to baking a seeded database image: no custom image or registry, just a snapshot file — good for local or small-team use where a shared registry is overkill. -If you're working on a project that can always start with a seeded database, you can actually check in the seed and it will always be used by default on an empty project. +If you're working on a project that can always start with a seeded database, you can actually check in the seed and it will always be used by default on an empty project. ```bash git add -f .ddev/db_snapshots/seed-* @@ -128,4 +129,3 @@ ddev restart --reset-database --omit-snapshot -y - MySQL 9.7 databases: - Example image builder [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) - Example invocation: `build-and-push-seeded-image.sh --snapshot=seed --output-image=randyfay/d11_normal:v1.25.4 --push --base-image=ddev/ddev-dbserver-mariadb-11.8:v1.25.4` - From 450c4e14d76f3a05a47b131eb9d962e74a9dd828 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Tue, 15 Sep 2026 09:25:12 -0600 Subject: [PATCH 6/7] Edits --- src/content/blog/ddev-snapshots.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index a53bb43d..85b43cee 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -92,7 +92,7 @@ Once you have a `seed` snapshot, `ddev restart --reset-database -Oy` repeatedly ## Building a Seeded Database Image -You can also create a replacement database image that has an alternate seed database built into it. This is especially great for delivering huge databases, as the process can be handled by the image, or an upstream process. +You can also create a replacement database image that has an alternate seed database built into it. This is especially great for delivering huge databases, as the process can be handled by the image, or an upstream process. All the image building does is copy a `base_db.zst` or `base_db.mbstream` into the `/mysqlbase/custom` directory of the DB image. For teams that want to share a ready-to-go database via a container registry instead of a snapshot file, [build-and-push-seeded-image.sh](https://github.com/rfay/database-performance/blob/main/scripts/build-and-push-seeded-image.sh) is an example that a real multi-arch (linux/AMD64, linux/ARM64) image with a snapshot baked in: From a289f7a26e79ed05d67ccb7e65bf3dbfc90be751 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Tue, 15 Sep 2026 09:30:33 -0600 Subject: [PATCH 7/7] TOC --- src/content/blog/ddev-snapshots.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/content/blog/ddev-snapshots.md b/src/content/blog/ddev-snapshots.md index 85b43cee..4426b37a 100644 --- a/src/content/blog/ddev-snapshots.md +++ b/src/content/blog/ddev-snapshots.md @@ -22,10 +22,7 @@ categories: ## Table of Contents - - - ## Snapshots are easy! A DDEV snapshot is a physical, "hot" backup of your database — `mariadb-backup`/`xtrabackup` for MariaDB and MySQL, or `pg_basebackup` for Postgres — not a text-based `mysqldump`. Because it copies the database's on-disk files instead of dumping SQL statements, it's much faster to create and restore, especially on large databases.