Running 88 Drupal Sites on Acquia Site Factory: An Operations Playbook

Acquia Site Factory (ACSF) is Acquia's product for running many Drupal sites from one codebase. The platform I work on has 88 of them: different brands and countries, each with its own database, domain, theme and editors, all deployed together from one repository to dev, test and live environments.

The code is the easy part. Most of the work is operations: deploying, running updates on every site, backups, cache, moving sites between environments, and helping someone who can't log in. This is the playbook that grew out of doing that for a few years.

Know your deploy types

On Site Factory you deploy a build to a whole stack, and you choose how much happens with it. The difference matters more than anything else on this list:

Most releases are code only. Database updates and config imports then run as separate, controlled steps, which is the next point.

One command, every site

The tool that makes a multisite manageable is acsf-tools. Its sfml command runs any Drush command on every site of an environment:

# run a Drush command on every site of an environment
drush @app.env sfml cr
drush @app.env sfml updb -y
drush @app.env sfml cim -y

After every deploy that includes config changes, the same four commands run in the same order. Search reindexing is easy to forget: the site works without it, but search results quietly go stale.

drush @app.live sfml updb -y -vvv
drush @app.live sfml search-api-reindex
drush @app.live sfml search-api-index
drush @app.live sfml cr

Not every command is safe to run on every site at once. Config import is the one that hurt us. In bulk it can run the server out of memory, so during big releases we raise the PHP memory limit temporarily, or import site by site.

When you SSH into an environment directly, remember that there is no "current site". Every Drush command needs --uri with the site's domain, otherwise you are talking to the default site.

The Site Factory API covers the rest

Some operations are not Drush commands: backups, clearing the platform-level cache, refreshing a site's external theme. The Site Factory UI can do them, but one site at a time. The API can do them for all sites, and the client for it is a few lines:

// lib/request.js
module.exports = async ({ url, method = "GET", data = null }) => {
  const res = await fetch(SITE_FACTORY_URL + url, {
    method,
    headers: {
      "Content-Type": "application/json",
      Authorization: "Basic " + process.env.KEY,
    },
    body: data,
  });
  return res.json();
};

// services/getAllSites.js
module.exports = async () => {
  const res = await request({
    url: "/api/v1/sites?stack_id=" + STACK + "&show_custom_domain=yes&limit=1000",
  });
  return res.sites;
};

The show_custom_domain=yes parameter is worth knowing. Without it you get the internal Site Factory domains, which are useless when you want to report on or test the real sites. With it, the same list is the source for almost every script I wrote for the platform, including the SearchStax migration.

Each operation is then a single small function:

const backup = (siteId) => request({
  url: "/api/v1/sites/" + siteId + "/backup",
  method: "POST",
  data: JSON.stringify({ components: ["database", "public files"] }),
});

const clearCache = (siteId) => request({
  url: "/api/v1/sites/" + siteId + "/cache-clear",
  method: "POST",
});

const refreshTheme = (siteId) => request({
  url: "/api/v1/sites/" + siteId + "/external-theme-refresh",
  method: "POST",
});

And a loop that does not hammer the platform. These calls start background tasks on Site Factory, and I didn't want 88 of them starting at the same moment. One every five seconds is gentle on the platform and still finishes in under ten minutes:

const sites = await getAllSites();

for (const site of sites) {
  try {
    console.log(JSON.stringify(await backup(site.id)));
  } catch (err) {
    console.error("failed: " + site.id, err);
  }
  await sleep(5000); // one task every 5 seconds
}

Before a risky release, a full backup of every site is now a single command instead of an afternoon of clicking.

Moving sites between environments

To get a production site onto test or dev, Site Factory has "staging": copying sites down from live to a lower environment. A few things worth knowing:

Getting a site onto your machine

For local work or a cloud IDE, acli pull --no-code pulls a chosen environment's database and files. Two notes. First, it pulls the latest backup, which may be older than you think. If the data looks stale, create a fresh database backup in Acquia Cloud first. Second, every site keeps its files under sites/<site id>/files, so the local settings need that path or no images will load:

// settings.local.php: serve one site's files locally
$settings['file_public_path'] = 'sites/<site id>/files';

When something breaks

Rules that saved us more than once

Takeaway

Running a large multisite isn't about knowing obscure Drupal internals. It's about turning every operation you do more than twice into one command, knowing which operations are safe to run everywhere at once, and writing down the rest.

← All posts