for Databases in Docker Dev Containers
Seed data is fundamentally different from plain init scripts: init scripts create the schema, seed data fills it with realistic, repeatable test data. Anyone mixing up both concepts ends up either with empty development databases or with inconsistent test states between team members.
Table of Contents
- 1. Why seed data is different from init scripts
- 2. Seed strategy: anonymized production dump
- 3. Seed strategy: programmatic seeders with Faker
- 4. Writing idempotent seed scripts correctly
- 5. Seed data as a dedicated service with docker compose run
- 6. Fast resets via volume snapshots instead of reseeding
- 7. Seed data for Magento specific test scenarios
- 8. Keeping seeds consistent in CI pipelines
- 9. Seed strategies compared
- 10. Summary
- 11. FAQ
1. Why seed data is different from init scripts
Init scripts in official MySQL or PostgreSQL images create the database schema on the container's first start, generating tables and indexes, and usually run exactly once. Seed data is conceptually separate from that: it fills an already existing schema with realistic data sets useful for development and manual testing, and unlike init scripts is meant to be repeatable as often as needed, for example when a developer accidentally broke test data and needs a clean starting state.
The difference becomes especially visible in Magento projects: the schema with all its EAV tables is created via setup:install or init scripts, but realistic seed data with hundreds of products, categories and test customers has to be loaded separately in order to meaningfully test checkout flows, search functionality or discount rules manually. Without well thought out seed data, every developer works with an almost empty database, hiding bugs that only surface at realistic data volumes.
This article shows different strategies for seed data in Docker dev containers, from anonymized production dumps through programmatic seeders to fast volume snapshots that enable a complete reset in seconds instead of minutes.
2. Seed strategy: anonymized production dump
The most realistic source for seed data is an export of the actual production database, though never unmodified, always with anonymized personal data. Names, email addresses, phone numbers and payment information must be replaced with placeholders or generated fake values before being used as local seed data, both for privacy reasons and to keep developers from accidentally handling real customer data.
#!/usr/bin/env bash
# anonymize-and-export.sh — create anonymized seed data from production
set -euo pipefail
PROD_DB="shop_production"
SEED_FILE="./seed/anonymized-dump.sql"
mysqldump --single-transaction --no-tablespaces "$PROD_DB" > /tmp/raw-dump.sql
# Anonymize personal data before it ever leaves the secure environment
mysql "$PROD_DB" <<'SQL'
UPDATE customer_entity
SET email = CONCAT('customer', entity_id, '@example.test'),
firstname = 'Test',
lastname = CONCAT('User', entity_id);
UPDATE sales_order_address
SET telephone = '000000000', street = 'Test Street 1';
SQL
mysqldump --single-transaction --no-tablespaces "$PROD_DB" > "$SEED_FILE"
echo "[OK] Anonymized seed data written to $SEED_FILE"
Important for this strategy: anonymization must happen before the export, never after, because a dump already exported with real data has already left the protected production environment. An anonymized dump as a source of seed data provides realistic data distributions and edge cases that synthetic generators often miss, for example unusually long product names or unusual price combinations.
3. Seed strategy: programmatic seeders with Faker
Where no production dump is available, for example on new projects without live data, programmatic seeders using libraries like Faker take over generating seed data. The advantage over a static SQL dump: the amount and structure of test data can be flexibly parameterized, for example "generate 500 products in 10 categories with 50 test customers", without maintaining a fixed file.
<?php
declare(strict_types=1);
namespace Mironsoft\DevTools\Console\Command;
use Faker\Factory as FakerFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Seeds the development database with realistic, reproducible test data.
* Intended to run inside the app container against the local dev database.
*/
final class SeedTestDataCommand extends Command
{
protected static $defaultName = 'dev:seed';
protected function execute(InputInterface $input, OutputInterface $output): int
{
// Fixed seed value keeps generated data identical across every run
$faker = FakerFactory::create('de_DE');
$faker->seed(42);
$pdo = new \PDO('mysql:host=db;dbname=shop_dev', 'root', 'secret');
for ($i = 0; $i < 500; $i++) {
$stmt = $pdo->prepare(
'INSERT INTO catalog_product_seed (sku, name, price) VALUES (?, ?, ?)'
);
$stmt->execute([
sprintf('SEED-%04d', $i),
$faker->words(3, true),
$faker->randomFloat(2, 5, 500),
]);
}
$output->writeln('[OK] Seeded 500 products with deterministic Faker data');
return Command::SUCCESS;
}
}
The decisive trick in this approach is $faker->seed(42): the fixed seed value makes Faker produce exactly the same seed data on every run, instead of delivering random new values each time. This makes bug reports reproducible, because a colleague running the same seed script gets exactly the same test data, instead of having to guess which randomly generated product triggered the observed error.
4. Writing idempotent seed scripts correctly
A common mistake in seed data scripts: they only work the first time they run and fail with duplicate key errors on every subsequent call, because they bluntly execute INSERT statements without regard for data already present. An idempotent seed script, in contrast, must be executable as often as needed and always end up in the same, defined final state, regardless of how many times it has run before.
-- WRONG: fails with duplicate key error on every run after the first
INSERT INTO catalog_product_seed (sku, name, price)
VALUES ('SEED-0001', 'Test Product', 19.99);
-- RIGHT: idempotent via TRUNCATE before re-seeding
TRUNCATE TABLE catalog_product_seed;
INSERT INTO catalog_product_seed (sku, name, price)
VALUES ('SEED-0001', 'Test Product', 19.99);
-- ALTERNATIVE: idempotent via upsert when truncation is not desired
INSERT INTO catalog_product_seed (sku, name, price)
VALUES ('SEED-0001', 'Test Product', 19.99)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
price = VALUES(price);
For seed data that should be fully controlled and reproducible, TRUNCATE before re-inserting is usually the more robust choice, since it guarantees an exactly defined final state. The upsert approach with ON DUPLICATE KEY UPDATE is better suited when additional, manually created test data should survive alongside the generated seed data.
5. Seed data as a dedicated service with docker compose run
Instead of manually exec-ing seed logic into a running container, it is worth having a dedicated Compose service solely responsible for loading seed data and stopping afterward. This service shares the network and database access with the main stack, but does not run permanently, instead being started specifically via docker compose run.
# docker-compose.yml — dedicated seeding service, not started with "up"
services:
app:
build: .
depends_on:
db:
condition: service_healthy
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: shop_dev
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
retries: 10
seed:
build: .
depends_on:
db:
condition: service_healthy
command: ["bin/console", "dev:seed"]
profiles: ["seed"]
# Start the stack normally without seeding
docker compose up -d
# Run seed data explicitly, only when actually needed
docker compose run --rm seed
The seed profile key ensures this service is never accidentally started along with a normal docker compose up. Instead, it only runs explicitly via docker compose run --rm seed, turning seed data runs into a deliberate, clearly visible action instead of an invisible side effect of a normal stack startup.
6. Fast resets via volume snapshots instead of reseeding
With larger data volumes, a complete reseed can take several minutes, which is disruptive in day to day development when a developer just wants to quickly get back to a clean starting state. It is much faster to back up already seeded seed data as a volume snapshot and simply restore that volume when needed, instead of running the entire seed script again.
#!/usr/bin/env bash
# snapshot-seed-data.sh — back up an already-seeded volume for instant restore
set -euo pipefail
VOLUME_NAME="shop-dev_db_data"
SNAPSHOT_DIR="./seed/snapshots"
mkdir -p "$SNAPSHOT_DIR"
echo "[INFO] Creating snapshot of seeded volume..."
docker run --rm \
-v "${VOLUME_NAME}:/source:ro" \
-v "$(pwd)/${SNAPSHOT_DIR}:/backup" \
alpine tar czf /backup/seeded-baseline.tar.gz -C /source .
echo "[OK] Snapshot saved to ${SNAPSHOT_DIR}/seeded-baseline.tar.gz"
#!/usr/bin/env bash
# restore-seed-data.sh — instant reset to the seeded baseline, seconds not minutes
set -euo pipefail
docker compose down
docker volume rm shop-dev_db_data || true
docker volume create shop-dev_db_data
docker run --rm \
-v "shop-dev_db_data:/target" \
-v "$(pwd)/seed/snapshots:/backup:ro" \
alpine tar xzf /backup/seeded-baseline.tar.gz -C /target
docker compose up -d
echo "[OK] Restored to seeded baseline in seconds"
This approach deliberately separates two different operations: the one time, potentially slow generation of seed data, and the frequent, always fast reset back to exactly that state. For developers needing a clean database state several times a day, for example during intensive manual testing, this pattern saves considerable waiting time compared to a complete reseed.
7. Seed data for Magento specific test scenarios
Magento already ships a baseline of seed data with its sample data packages, sufficient for a first impression but rarely covering the specific test scenarios of a real project. For realistic testing of discount rules, multi level category trees or multi website setups, project specific seed data is needed, loaded via custom data patches or CLI commands instead of relying on the generic sample data.
A proven pattern is a custom bin/magento command that specifically creates test customers with different customer groups, products with special prices, and orders in various statuses. This project specific seed data mirrors real business logic, such as tiered B2B discounts or seasonal offers, and thereby surfaces bugs that would never show up with Magento's generic sample data.
8. Keeping seeds consistent in CI pipelines
The same seed data used locally for manual testing should ideally also be used in the CI pipeline for automated end to end tests. A central seed script that runs identically both locally and on the CI runner prevents the situation where a test passes locally but fails in CI because different test data is present there than on the developer's machine.
Important here: CI environments should never use the full anonymized production dump, since that is too large and too slow for fast CI runs. A more compact subset of the seed data is better suited for CI, generated via the same Faker seeder with the same fixed seed values, but with significantly reduced data volume, for example 50 instead of 500 products, to keep pipeline runtimes short.
9. Seed strategies compared
Choosing the right seed strategy depends heavily on the project stage and the available data sources.
| Strategy | Realism | Setup effort | Speed |
|---|---|---|---|
| Anonymized production dump | Very high | High (anonymization required) | Slow with large dumps |
| Faker seeder | Medium | Medium | Fast and parameterizable |
| Volume snapshot | Same as snapshot source | Low after initial setup | Very fast (seconds) |
For maximum realism on complex bug reports, the anonymized production dump is unbeatable, but costs the most setup effort due to the mandatory anonymization. Faker seeders quickly deliver parameterizable seed data for new projects without production history. Volume snapshots are not a standalone data generator, but a speed optimization for frequent resets of already existing seed data.
Mironsoft
Seed scripts, test data strategies and Docker development environments for Magento
Realistic test data for your whole team?
We build anonymized dump pipelines, idempotent Faker seeders and volume snapshot workflows for your Magento and PHP projects, so every developer works with the same, reproducible seed data.
Anonymization pipeline
Secure, GDPR compliant creation of seed data from production data
Faker seeder development
Idempotent, parameterizable seed scripts for new projects without production history
Volume snapshot workflow
Second fast resets to a clean, seeded starting state
10. Summary
Seed data solves a different problem than init scripts: instead of creating the schema, it fills a database with realistic, repeatable test data for development and manual review. Anonymized production dumps deliver maximum realism, programmatic Faker seeders with a fixed seed value deliver reproducible, parameterizable data volumes without production history, and idempotent scripts with TRUNCATE or upsert ensure repeated seeding never fails with duplicate key errors.
Volume snapshots complement these strategies with fast resets in seconds instead of minutes, while dedicated Compose services with a profile flag turn seed data runs into a deliberate, visible action instead of an invisible side effect. Anyone using the same seed mechanisms consistently between local development and the CI pipeline avoids the frustrating situation where a test passes locally but fails in CI, simply because different seed data was underlying it.
Seed Data for Docker Dev Containers — The Essentials at a Glance
Init vs. seed
Init scripts create the schema, seed data fills it with realistic, repeatably loadable test data.
Anonymization
Always replace personal data before export, never distribute an unprotected dump.
Idempotence
TRUNCATE before reseeding or ON DUPLICATE KEY UPDATE prevents errors on repeated runs.
Volume snapshots
Back up an already seeded volume and restore it in seconds instead of reseeding every time.