How to run Doctrine migrations in Symfony

Doctrine migrations turn reviewed PHP migration classes into schema changes on the database used by a Symfony application. Running them is part of a release because the code and database shape must reach the same version before new entities, repositories, or message-backed tables are used.

Symfony Console exposes Doctrine Migrations through php bin/console, and the command reads the same DATABASE_URL and environment as the application. During deployment, APP_ENV=prod should already point the console at the production connection; in local rehearsal, a SQLite URL can keep the run disposable.

Run only migration files that have been reviewed and committed, and take a database backup or rollback snapshot before schema changes that are hard to reverse. A controlled pass checks pending versions, dry-runs SQL at verbose output, applies the migration without an interactive prompt, and verifies both the version table and the affected schema.

Steps to run Doctrine migrations in Symfony:

  1. Open a terminal in the Symfony project root that contains bin/console and the reviewed migration file.

    If the deployment shell does not already export APP_ENV=prod, select the production environment for each console invocation so Doctrine uses the intended database connection.

  2. Review the pending migration class for the SQL that will run.
    migrations/Version.php
    public function up(Schema $schema): void
    {
        $this->addSql(
            'CREATE TABLE product (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(80) NOT NULL)'
        );
     
        // Review every additional SQL statement before running the migration.
    }

    Pause before running if the migration drops data, rewrites large tables, adds required columns to existing rows, or needs an application rollback plan.

  3. List available migration versions before applying them.
    $ php bin/console doctrine:migrations:list
    +------------------------------------------+--------------+-------------+----------------+-------------+
    | Migration Versions                                                                     |             |
    +------------------------------------------+--------------+-------------+----------------+-------------+
    | Migration                                | Status       | Migrated At | Execution Time | Description |
    +------------------------------------------+--------------+-------------+----------------+-------------+
    | DoctrineMigrations\Version20260625075716 | not migrated |             |                |             |
    +------------------------------------------+--------------+-------------+----------------+-------------+
  4. Dry-run the pending SQL with verbose output.
    $ php bin/console doctrine:migrations:migrate --dry-run --no-interaction -vv
    [notice] Migrating (dry-run) up to DoctrineMigrations\Version20260625075716
    [info] ++ migrating DoctrineMigrations\Version20260625075716
    [debug] CREATE TABLE product (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(80) NOT NULL)
    ##### snipped #####
    [notice] finished in 3.3ms, used 24M memory, 1 migrations executed, 5 sql queries
     
     [OK] Successfully migrated to version: DoctrineMigrations\Version20260625075716

    -vv prints the SQL emitted during the dry run. Paste the SQL into the migration risk checker before production when the migration touches large or hot tables.
    Tool: Database Migration Risk Checker

  5. Run the migration without an interactive confirmation prompt.
    $ php bin/console doctrine:migrations:migrate --no-interaction
    [notice] Migrating up to DoctrineMigrations\Version20260625075716
    [notice] finished in 3.3ms, used 24M memory, 1 migrations executed, 3 sql queries
     
     [OK] Successfully migrated to version: DoctrineMigrations\Version20260625075716

    Do not use --no-interaction as a shortcut for skipping review. It belongs in deployment automation after the target database, backup, and migration SQL have already been checked.

  6. Confirm that Doctrine now marks the version as migrated.
    $ php bin/console doctrine:migrations:list
    +------------------------------------------+----------+---------------------+----------------+-------------+
    | Migration Versions                                                                         |             |
    +------------------------------------------+----------+---------------------+----------------+-------------+
    | Migration                                | Status   | Migrated At         | Execution Time | Description |
    +------------------------------------------+----------+---------------------+----------------+-------------+
    | DoctrineMigrations\Version20260625075716 | migrated | 2026-06-25 07:57:26 | 0.001s         |             |
    +------------------------------------------+----------+---------------------+----------------+-------------+
  7. Check the migration version table through Doctrine DBAL.
    $ php bin/console dbal:run-sql 'SELECT version FROM doctrine_migration_versions'
     ------------------------------------------ 
      version                                   
     ------------------------------------------ 
      DoctrineMigrations\Version20260625075716  
     ------------------------------------------ 
  8. Verify the affected table or column exists.
    $ php bin/console dbal:run-sql "SELECT name, type, [notnull] AS required FROM pragma_table_info('product')"
     ------ ------------- ---------- 
      name   type          required  
     ------ ------------- ---------- 
      id     INTEGER       1         
      name   VARCHAR(80)   1         
     ------ ------------- ---------- 

    Use the equivalent inspection query for the production database engine when the migration targets MySQL, MariaDB, PostgreSQL, or another server-backed database.