Skip to content

Powered by Grav

Migrating a Drupal 7 site to Drupal CMS

Migrating a Drupal 7 site to Drupal CMS

Your BOA server is the ideal migration rig, because it hosts both ends at once: the Drupal 7 site that keeps serving your visitors, and — right next to it, in the same Octopus instance — a fresh Drupal CMS site that Drupal's own migration engine fills with the Drupal 7 site's content, files and users. You rehearse the run as many times as you like on disposable copies, and when the result is right you swap the domain over. The live site is never in the write path.

Two different "migrates" on this page. Ægir's Migrate Site task moves a site between platforms or renames its domain — it never changes content. Drupal's Migrate API — the engine behind core's /upgrade tool — reads a Drupal 7 database and rebuilds that content inside a modern Drupal site. You use the Migrate API for the crossing, and the Migrate Site task at the very end, purely as a rename.

What crosses, what doesn't. Content, files, taxonomy and users cross. Your Drupal 7 theme and custom modules don't — the front end is a fresh start on Drupal CMS's toolset, and that part is a project of its own. What this page gives you is the safe, repeatable machinery around the crossing.

Step 1 — Make sure the Drupal CMS platform is there

Drupal CMS ships in BOA's distribution catalogue, and on a stock server — _PLATFORMS_LIST=ALL, the shipped default in /root/.<user>.octopus.cnf — the platform is already installed and registered in your panel: look for the version-numbered Drupal CMS entry in the Platform drop-down of the create-site form. It's rebuilt from the upstream Composer template as BOA releases ship, so it tracks the version the Drupal Association currently publishes.

If you've narrowed _PLATFORMS_LIST to specific symbols, add CMS to the list and run your normal upgrade pair so the platform installs:

TXT
barracuda up-lts system
octopus up-lts o1

(up-pro on PRO instances — whichever tree the server already follows.)

Both ends must live in the same Octopus instance. Each instance is a deliberate isolation boundary — a site's PHP can only read inside its own instance's tree — so a Drupal CMS target on o1 cannot import from a Drupal 7 site on o2. If your Drupal 7 site lives in another instance (or on another server), bring a copy into the target's instance first: restore it there from a backup, or register its codebase and import the site the usual Ægir way.

Step 2 — Install the target site

In your Ægir panel, create a new site on the Drupal CMS platform:

  • Domain: use a dev.-prefixed rehearsal name — dev.newsite.example.com. Crawlers and bots get a 404 on dev. names, so search engines never see your half-migrated rehearsals — and unlike deeper test labels, a first-label dev. name can still get a real HTTPS certificate if you enable HTTPS on it. The production name comes later, by rename.
  • Install profile: pick the Drupal CMS profile, drupal_cms_installer — the distribution installs itself around it.

The install runs unattended and lands on a deliberately plain front page — on BOA the panel installs the complete Drupal CMS package headlessly, so the interactive setup wizard you may have seen elsewhere never runs. A plain front page is a healthy install; log in and the full toolset is there.

Step 3 — Switch on the core migration modules

Drupal's upgrade tool ships in core, disabled. On the target site, log in as the site admin (the Log in button on the site's panel page gets you there), open Extend (/admin/modules) and under the Migration group enable:

  • Migrate
  • Migrate Drupal
  • Migrate Drupal UI

Two things you'll see are normal. The last two are labelled (Deprecated) — upstream's label, meaning they're headed for contrib in some future major release; today they are the supported Drupal 7 upgrade path, and core asks you to confirm installing deprecated modules — continue. The confirmation also adds Password Compatibility for you: it lets imported users keep logging in with their existing Drupal 7 passwords.

One more decision on this page: any content feature the Drupal 7 site uses must be enabled here before you migrate, or its data is skipped. The headline example is Comment — Drupal CMS deliberately ships without it, so a Drupal 7 site with comments needs Comment enabled now. The upgrade's review screen (Step 5) shows exactly which of the old site's modules have nowhere to land, so you don't need the list perfect on the first pass.

One last piece of target preparation: clear out the starter content. Open Content (/admin/content) and delete the content items a fresh Drupal CMS ships with (there's a draft Privacy policy page). Imported Drupal 7 content keeps its original ID numbers, and an item whose ID is already taken by an existing content item fails to import rather than replacing it — silently costing you your oldest content. An empty content list means every ID is free. (The Home page is unaffected — it isn't a content item.)

The upgrade tool is now live at /upgrade on the target site.

Step 4 — Prepare the source and collect its coordinates

The upgrade form needs the Drupal 7 site's database credentials and its platform's document root. As root, both are one look away — every BOA site's settings.php carries its own literal credentials:

SH
grep -A8 "databases\['default'\]" \
  /data/disk/o1/distro/002/drupal-7.105.2-prod/sites/www.example.com/settings.php

Adjust the instance (o1) and platform directory to yours — the site's Drupal root as shown by drush @www.example.com status run as the instance user. Note the root path exactly: the upgrade form wants this real absolute path, because it's the target site's PHP that reads the source files during the import.

Then one preparation command, and it's the step everyone would otherwise lose an afternoon to. Ægir keeps a site's files location in settings.php rather than in the Drupal 7 database — but the upgrade tool reads the database — so without help it assumes the stock sites/default/files and quietly imports no files at all. Mirror the real value into the database (as the instance user):

SH
drush @www.example.com vset file_public_path sites/www.example.com/files

This is safe on the live site: settings.php keeps overriding the setting at runtime, so behaviour doesn't change — the value just becomes visible to the migration. If the site uses private files, mirror file_private_path the same way.

Step 5 — Run the upgrade

On the target site, open /upgrade and follow the wizard:

  1. Version — pick 7.
  2. Source database — the credentials from Step 4: database name, username, password, host and port exactly as settings.php shows them.
  3. Files — as Document root for public files, give the source platform's absolute root path. With the file_public_path mirror from Step 4 in place, the importer resolves each upload's real location under that root and copies it across.
  4. The conflict warning. Before the report, the tool warns that content may be overwritten and asks you to press "I acknowledge I may lose data. Continue anyway." It fires because the target isn't a bare Drupal — Drupal CMS ships starter menu links and files, and the import replaces the ones whose ID numbers collide with incoming items. On a rehearsal target that's exactly what you want — you cleared the content items in Step 3, and the remaining starter menu links and files are placeholders. Acknowledge and continue. (This is also why you migrate into a fresh site, never one you've started building by hand.)
  5. Review the "What will be upgraded?" report. Two tables: the old site's modules that will be upgraded, and those that won't — modules with no counterpart installed on the new site, whose configuration and content would be skipped. If something you care about is in the wrong table (remember Comment from Step 3), go back to Extend, enable the counterpart, and re-enter the wizard — the report updates to match. When the tables look right, press Perform upgrade.
  6. Watch it run. Configuration imports first, then content. Minutes for a modest site; large media libraries take longer.

Step 6 — Review what landed

The batch drops you back on the target, and the first impression is delightfully strange: the site now believes it's your Drupal 7 site — its name, slogan and settings crossed over — while wearing Drupal CMS's theme. Walk it as an editor and as a visitor: content in place, images attached, menus sane, users present (old passwords keep working). Classic artifacts to expect and fix:

  • The front page may 404. Drupal 7's default front page is the /node listing, and that setting migrates — but the target has no /node page. Point the front page somewhere real under Configuration → Basic site settings.
  • Basic pages may arrive unpublished. A content type that already exists on the target — Basic page being the everyday case — receives imported items through Drupal CMS's editorial workflow, and they can land as drafts; types recreated from the source (a Drupal 7 Article) keep their published state. Review Content and publish what should be live.
  • A few broken block placements. Block positions migrate from a theme the target doesn't have, so odd corners may show "This block is broken or missing" — remove or re-place them under Structure → Block layout.
  • The review tables told the truth. Anything the report listed as not upgraded really isn't there — your worklist for the next rehearsal.

Step 7 — Iterate on disposable targets

One rule shapes the rehearsal loop, and the tool states it itself if you revisit /upgrade: an upgrade runs once per site. There's no re-running it on the same target to top up or retry — the UI refuses, and rollbacks aren't supported through it. That's what the panel's cheap site tasks are for:

  • Iterate by replacing the target. Adjust whatever the last run taught you, run Delete Site on the target, repeat Steps 2–5 on a fresh one, and compare. Every rehearsal is as clean as the first, and the Drupal 7 site serves visitors untouched throughout.
  • Freeze the source if you want runs comparable. The importer reads the source's live database. For a fixed reference, Clone the Drupal 7 site to a dev. name and point the upgrade form at the clone's credentials (its own settings.php, same platform root — and run the Step 4 vset against the clone). Clones land with cron off by design, so the copy holds perfectly still between runs.

Step 8 — Cut over by rename

Because the migration runs once per target, the final run is the cutover build: when a rehearsal has proven the process end to end, do one last fresh run at a quiet moment — content editing on the Drupal 7 site paused briefly — and that target becomes the site. The swap is two renames with Ægir's Migrate Site task — a domain can only be held by one site at a time, so free the name first:

  1. Rename the Drupal 7 site out of the way: Migrate Site, change only the Domain nameold.example.com. It stays installed and serving there as your fallback and archive.
  2. Rename the target into place: Migrate Site on the Drupal CMS site, change only the Domain name, to the production domain.
  3. Re-enable HTTPS for the renamed site — a rename always switches HTTPS off, because the certificate belonged to the old name. Turn it back on and a fresh certificate is issued for the production domain.
  4. Enable the new site's scheduled cron, and disable it on the retired Drupal 7 site.

Keep the retired Drupal 7 site at its old. name until you're certain, then take a final backup and delete it at your leisure.

The command-line lane (advanced)

The wizard is the everyday path, but the Drupal CMS platform also ships Drupal's full migration pipeline as code — migrate_plus, migrate_tools and migrate_upgrade sit in the platform's codebase, switched off — so the whole crossing can be driven from the shell instead: scriptable, repeatable, and re-runnable per migration. The commands run through the site's own bundled Drush: run the Unlock Local Drush task on the Drupal CMS platform first (Ægir keeps vendor/drush locked read-only between uses), connect as the instance's .ftp user, cd to the platform root (the directory holding vendor/) as its own command — the limited shell refuses cd ... && vdrush ... one-liners — and re-lock with Lock Local Drush when done.

This lane is also where the Migration source task earns its keep: run it on the target site, pick the Drupal 7 source, and the source database arrives as a named migrate connection — read-only, enforced by the database server, with no credentials collected or pasted anywhere. The task is switched on automatically by every Octopus install and upgrade: Migration source wiring covers exactly what it grants, what bounds it, and the manual enabling pair for an instance that predates the automatic path. It wires the database, not the files, so the document root and the Step 4 file_public_path preparation stay yours:

SH
vdrush @dev.newsite.example.com pm:install migrate migrate_drupal migrate_plus migrate_tools migrate_upgrade
vdrush @dev.newsite.example.com migrate:upgrade --legacy-db-key=migrate --legacy-root=/data/disk/o1/distro/002/drupal-7.105.2-prod --configure-only
vdrush @dev.newsite.example.com migrate:status --group=migrate_drupal_7
vdrush @dev.newsite.example.com migrate:import --group=migrate_drupal_7 --continue-on-failure

Without the task, the same command takes the Step 4 credentials directly, with the host and port exactly as settings.php shows them: --legacy-db-url="mysql://DBUSER:DBPASS@DBHOST:DBPORT/DBNAME" in place of --legacy-db-key=migrate.

Migrate Drupal UI isn't needed in this lane; the Step 4 file_public_path preparation applies unchanged; and for a site created moments ago whose alias drush11 aliases doesn't list yet, replace the alias with --root=<platform root>/web --uri=dev.newsite.example.com. --configure-only is this lane's review screen — it derives one migration per piece of the old site without importing, and migrate:status shows the set with row counts read straight from the Drupal 7 database. Always import with --continue-on-failure: without it, one failed row halts the whole group.

Two behaviours look like breakage and have routine fixes. A missing counterpart can stop the run mid-group — the classic case is a Drupal 7 site with comments crossing into comment-less Drupal CMS: the import aborts ("Plugin ID 'd7_comment_type' was not found") and the interrupted migration is left showing Importing, which blocks the next run. Enable the counterpart (pm:install comment), re-run the migrate:upgrade --configure-only command to re-derive the set, reset the stuck migration (migrate:reset-status upgrade_d7_field_instance, or whichever the status table shows stuck), and import again. And partial counts are the report, not a failure — Drupal 7 actions, blocks and theme settings whose destination doesn't exist on Drupal CMS fail or are skipped row by row; read migrate:messages upgrade_<name> for anything surprising. It's the content migrations — nodes, users, terms, files, aliases — that should reach their totals.

Unlike the wizard, this lane isn't once-per-site: re-running migrate:import picks up unprocessed rows and leaves imported migrations alone, so an interrupted run simply continues. Step 6's review list applies to this lane unchanged.

The other exit from Drupal 7

The Migrate-API road rebuilds the site around your content. If what you want instead is continuity — same structure, same theme lineage, no rebuild — BOA's panel also ships the Drupal 7 → Backdrop upgrade, which converts a copy of the site wholesale. Both exits stay open; nothing on this page commits you to either.

© 2026 BOA Documentation. All rights reserved.