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:
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 ondev.names, so search engines never see your half-migrated rehearsals — and unlike deeper test labels, a first-labeldev.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:
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):
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:
- Version — pick 7.
- Source database — the credentials from Step 4: database name,
username, password, host and port exactly as
settings.phpshows them. - Files — as Document root for public files, give the source
platform's absolute root path. With the
file_public_pathmirror from Step 4 in place, the importer resolves each upload's real location under that root and copies it across. - 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.)
- 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.
- 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
/nodelisting, and that setting migrates — but the target has no/nodepage. 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 ownsettings.php, same platform root — and run the Step 4vsetagainst 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:
- Rename the Drupal 7 site out of the way: Migrate Site, change only
the Domain name —
old.example.com. It stays installed and serving there as your fallback and archive. - Rename the target into place: Migrate Site on the Drupal CMS site, change only the Domain name, to the production domain.
- 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.
- 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:
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.