Migrations
Vortex provides optional support for a second database to power Drupal
migrations. When enabled, a database2 Docker service runs alongside the
primary database, and a settings.migration.php file registers the
$databases['migrate'] connection that Drupal's Migrate API uses by default.
Enabling the migration database
Via the installer
When running the Vortex installer, answer Yes to "Use a second database for migrations?" and select a download source.
Manual setup
If you already have a Vortex project and want to add migration support:
- Ensure
docker-compose.ymlcontains thedatabase2service andDATABASE2_*environment variables (copy from a fresh Vortex install with migration enabled). - Add
VORTEX_FETCH_DB2_FILE,VORTEX_FETCH_DB2_SOURCE, andVORTEX_FETCH_DB2_URLto.env. - Create
web/sites/default/settings.migration.php(see below). - Add the include in
web/sites/default/settings.php. - Ensure the
ys_migratemodule (with itsMigrateContentDeployStepplugin) is present.
Environment variables
| Variable | Default | Description |
|---|---|---|
VORTEX_FETCH_DB2_FILE | db2.sql | Migration database dump file name |
VORTEX_FETCH_DB2_SOURCE | url | Download source (url, ftp, acquia, lagoon, s3) |
VORTEX_FETCH_DB2_URL | (empty) | URL to download the migration database dump |
DATABASE2_HOST | database2 | Migration database host |
DATABASE2_NAME | drupal | Migration database name |
DATABASE2_USERNAME | drupal | Migration database user |
DATABASE2_PASSWORD | drupal | Migration database password |
DATABASE2_PORT | 3306 | Migration database port |
Docker service
The database2 service uses the same Lagoon MySQL image as the primary
database but does not use a custom Dockerfile or database-in-image storage.
database2:
image: uselagoon/mysql-8.4:26.1.0
environment:
<<: *default-environment
MYSQL_DATABASE: drupal
MYSQL_USER: drupal
MYSQL_PASSWORD: drupal
ports:
- '3306'
labels:
lagoon.type: none
The service is labeled lagoon.type: none because the migration database
is only used locally and in CI — it is not deployed to hosting environments.
Drupal settings
settings.migration.php
$databases['migrate']['default'] = [
'database' => getenv('DATABASE2_NAME') ?: 'drupal',
'username' => getenv('DATABASE2_USERNAME') ?: 'drupal',
'password' => getenv('DATABASE2_PASSWORD') ?: 'drupal',
'host' => getenv('DATABASE2_HOST') ?: 'localhost',
'port' => getenv('DATABASE2_PORT') ?: '',
'prefix' => '',
'driver' => 'mysql',
];
Drupal's Migrate API SqlBase source plugin uses key: migrate by default,
which resolves to this connection.
Ahoy commands
| Command | Description |
|---|---|
ahoy fetch-db2 | Download the migration database dump |
ahoy fetch-db2 --fresh | Force a fresh download |
ahoy db2 | Open the migration database in Sequel Ace |
The fetch-db2 command reuses the existing fetch-db.sh script with
VORTEX_DB_INDEX=2, which makes all scripts resolve indexed variable names
(e.g., VORTEX_FETCH_DB2_SOURCE instead of VORTEX_FETCH_DB_SOURCE,
VORTEX_DB2_IMAGE instead of VORTEX_DB_IMAGE) so all existing download
sources (URL, FTP, Acquia, Lagoon, S3) work for the migration database as well.
Migration deploy step
The migration runs as the MigrateContentDeployStep plugin in the ys_migrate
module (src/Plugin/DeployStep/MigrateContentDeployStep.php). It runs on every
drush deploy:hook in non-production environments - so it runs during
ahoy provision and on every deploy. See Deployment for how
deploy step plugins work.
It handles:
- Importing the migration source database dump into the
database2container - Corruption detection (probes for a known table to verify the database)
- Running Drupal migrations via
drush migrate:import(in a memory-bounded, resumable subprocess) - Optional rollback before import
Configuration variables
The plugin reads these environment variables (via getenv()):
| Variable | Default | Description |
|---|---|---|
DRUPAL_MIGRATION_SKIP | 0 | Skip all migration operations |
DRUPAL_MIGRATION_ROLLBACK_SKIP | 1 | Skip rollback before import |
DRUPAL_MIGRATION_IMPORT_LIMIT | 50 | Limit entities per migration (use all for unlimited) |
DRUPAL_MIGRATION_UPDATE | 0 | Update already imported entities |
DRUPAL_MIGRATION_FEEDBACK | 50 | Progress feedback frequency |
DRUPAL_MIGRATION_SOURCE_DB_IMPORT | $VORTEX_PROVISION_OVERRIDE_DB | Import source database (1 to import, 0 to skip) |
DRUPAL_MIGRATION_SOURCE_DB_PROBE_TABLE | categories | Table name to probe for corruption detection |
Adding migrations
Add your migration IDs to the plugin's MIGRATIONS constant:
protected const MIGRATIONS = ['ys_migrate_categories'];
Corruption detection
If DRUPAL_MIGRATION_SOURCE_DB_IMPORT is 0, the plugin probes the source database
for the table specified in DRUPAL_MIGRATION_SOURCE_DB_PROBE_TABLE. If the table is
missing (corrupted or empty database), the plugin automatically re-imports the
dump file.
Demo migration module
Vortex ships with a demo migration module ys_migrate in
web/modules/custom/ys_migrate/. It demonstrates the full migration workflow
by migrating categories from the source database into Drupal's tags
taxonomy vocabulary.
Migration: ys_migrate_categories
| Property | Value |
|---|---|
| Source plugin | ys_migrate_categories (reads from categories table) |
| Source key | migrate (the $databases['migrate'] connection) |
| Destination | entity:taxonomy_term |
| Vocabulary | tags |
The migration maps:
name→ term namedescription→ term description (plain text format)
Dependencies
The module requires drupal/migrate_plus and drupal/migrate_tools (added
to composer.json when the migration feature is enabled).
CI integration
Both CI systems (GitHub Actions and CircleCI) include a migration database
download step in the existing database job. Both database files are cached
together in the same .data directory.
The build job copies the migration database file into the CLI container
alongside the primary database before running provision.
Example workflow
-
Set
VORTEX_FETCH_DB2_URLin.env(or.env.local):VORTEX_FETCH_DB2_URL=https://example.com/legacy-database.sql -
Download the migration database:
ahoy fetch-db2 -
Build and provision:
ahoy build -
Define the migration in the
ys_migratemodule (amigrations/*.ymlfile ormigrate_plusconfig). TheMigrateContentDeployStepplugin runsmigrate:import --all, so any migration the module provides is imported automatically - no plugin change needed. -
Re-provision to run migrations:
ahoy provision