Local environment for Acquia Site Factory

  • Last updated
  • 1 minute read

Goal

Set up a best practice local environment for an Acquia Site Factory platform.

Overview

The purpose of this tutorial is to explain how to set up a best practice local environment for Drupal multisite, including on Acquia Site Factory:

  1. Configure Lando to support wildcard DNS.
  2. In settings.php, set an "App ID" to use in code for a specific multisite in any environment.
  3. In settings.php, set database, public files, memcache prefix, Solr core, and other settings per "App ID".
  4. Configure Drush aliases per multisite and per environment.
  5. Write bash scripts to push and pull sites using these aliases.
  1. Configure Lando with wildcard DNS

    Lando is a docker orchestration framework for development environments.

    There are alternatives to Lando like ddev or docksal, but my preference is Lando because:

    1. It's simple to configure with YAML.
    2. It supports many other services like memcache, solr, redis, elasticsearch, mssql, etc.
    3. It runs on Macs, Windows, or Linux
    4. Their homepage is fancy.

    If you're unfamiliar with Lando, reference their documentation before continuing here.

    Acquia supports a "Lando recipe" that configures a set of Docker services that resembles an Acquia Cloud environment with services for Apache / php-fpm, MySQL, memcache, and Solr. This recipe can be simply extended to support multisite, including multisite for Acquia Site Factory.

    If you're using Cloud IDE, it does not support running multiple sites at once because it only has 1 hostname. It is possible to work around this with path based multisites, but we can cover that in a different tutorial if requested.

    The below example shows:

    1. Lando configuration using Acquia recipe
    2. Wildcard DNS for any subdomain of *.client1.lndo.site
    3. Other useful tips

    Copy this to the project root of your Site Factory platform and run: lando start

    name: client1
    recipe: acquia
    config:
     acli_version: latest
     ah_application_uuid: enter-a-valid-guid # Found in URL of Acquia Cloud.
     ah_site_group: acquia-machine-name # Found in git URL.
     php: '8.1'
     xdebug: true
    proxy:
     appserver:
       - client1.lndo.site
       - "*.client1.lndo.site" # Wildcard here supports any subdomain of client1.
     solr:
       - solr.client1.lndo.site:8955
    services:
     appserver:
       xdebug: true
       overrides:
         environment:
           XDEBUG_CONFIG: "client_host=host.docker.internal discover_client_host=1 log=/tmp/xdebug.log remote_enable=true remote_host=host.docker.internal"
     solr:
       type: solr:7
       portforward: 8955 # Needs to match the port in: config.proxy.solr
       core: lando
     database:
       portforward: 33065 # Allows you to connect to database from local client.
    
  2. In settings.php, set an "App ID" to use in code for a specific multisite in any environment

    It's very helpful to have an "App ID" or unique identifier per multisite:

    1. Used in Drush aliases per environment
    2. Used in settings.php for config overrides and settings
    3. Used in custom code if necessary

    Add the below code to the top of settings.php to map an environment variable to this "App ID". If you're using Acquia Site Factory, ensure this settings.php is loaded from a post-settings-php factory hook.

    The "App ID" is determined by the first piece of the hostname. This is the "site name" on Acquia Site Factory and is consistent between all environments, including in this Lando environment.

    $parts = explode('.', $_SERVER['HTTP_HOST']);
    if ($parts[0] == 'www') {
      $aid = $parts[1];
    }
    else {
      $aid = $parts[0];
    }
    $_ENV['SYS_AID'] = $aid;

    We'll use this environment variable in the next steps.

  3. In settings.php, set database, public files, memcache prefix, Solr core, and other settings per "App ID"

    Once your "App ID" is set, configure Lando resources per multisite. This sets the database name, public files directories, and memcache prefix to the "App ID".

    // Configure unique database per multisite. The root user can access or create
    // any database in Lando.
    $databases['default']['default'] = [
      'database' => $_ENV['SYS_AID'],
      'username' => 'root',
      'password' => '',
      'host' => 'database',
      'port' => '3306',
      'driver' => 'mysql',
      'prefix' => '',
      'collation' => 'utf8mb4_general_ci',
    ];
    
    // Set public files path per multisite within the default files directory.
    $settings['file_public_path'] = "sites/default/files/{$_ENV['SYS_AID']}";
    
    // Set temporary files path per multisite.
    $settings['file_temp_path'] = "/tmp/{$_ENV['SYS_AID']}";
    
    // Set private files path per multisite. This could also live within
    // the directory /app/files-private.
    $settings['file_private_path'] = "/tmp/private-{$_ENV['SYS_AID']}";

    If you need a Solr core per multisite, add these lines to your settings.php and .lando.yml:

    settings.php

    $config['search_api.server.acquia_search_server']['backend_config']['connector_config']['core'] = 'http';
    $config['search_api.server.acquia_search_server']['backend_config']['connector_config']['host'] = 'solr';
    $config['search_api.server.acquia_search_server']['backend_config']['connector_config']['port'] = 8983;
    $config['search_api.server.acquia_search_server']['backend_config']['connector_config']['core'] = $_ENV['SYS_AID'];

    .lando.yml (create a core for each multisite)

    services:
      solr:
        type: solr:7
        portforward: 8955
        core: lando
        run:
          - /opt/solr/bin/solr create -c client1 -d /solrconf/conf
          - /opt/solr/bin/solr create -c client2 -d /solrconf/conf
  4. Configure Drush aliases per multisite and per environment

    Acquia provides Drush aliases per environment you can find at:

    1. The "Credentials" tab of your Acquia Cloud account
    2. Using "acli remote:aliases:download" on command line.
      1. Ensure Acquia application UUID is set in .acquia-cli.yml at project root with config key "cloud_app_uuid".

    But these aliases are not per multisite. You can pass the multisite URL per environment in the --uri option to Drush commands, but certain commands like "sql:sync" don't support the URI option and require specific aliases. And it's a hassle.

    As a best practice, modify your Drush aliases file to have an alias per multisite per environment. It's super helpful both for manual use on CLI to target specific sites and for use in bash scripts and automation.

    If it's a small number of sites, it's reasonable to do manually. If it's a large number of sites, you can write a script to do it. The acquia/acsf-tools project provides a YAML file you can use in a script with all multisites.

    The below example shows multisite aliases for App IDs "site1" and "site2" of application "client1". After saving this, discover Drush aliases with: drush sa

    local-site1:
      root: /app/docroot
      uri: 'https://site1.client1.lndo.site/'
    
    local-site2:
      root: /app/docroot
      uri: 'https://site2.client1.lndo.site/'
    
    01live-site1:
      uri: https://site1.client1.acsitefactory.com/
      root: /var/www/html/client1.01live/docroot
      ac-site: client1
      ac-env: 01live
      ac-realm: enterprise-g1
      01live.livedev:
        parent: '@client1.01live'
        root: /mnt/gfs/client1.01live/livedev/docroot
      host: client101live.ssh.enterprise-g1.acquia-sites.com
      user: client1.01live
    
    01live-site2:
      uri: https://site2.client1.acsitefactory.com/
      root: /var/www/html/client1.01live/docroot
      ac-site: client1
      ac-env: 01live
      ac-realm: enterprise-g1
      01live.livedev:
        parent: '@client1.01live'
        root: /mnt/gfs/client1.01live/livedev/docroot
      host: client101live.ssh.enterprise-g1.acquia-sites.com
      user: client1.01live
    
    01test-site1:
      uri: https://site1.test-client1.acsitefactory.com/
      root: /var/www/html/client1.01test/docroot
      ac-site: client1
      ac-env: 01test
      ac-realm: enterprise-g1
      01test.livedev:
        parent: '@client1.01test'
        root: /mnt/gfs/client1.01test/livedev/docroot
      host: client101test.ssh.enterprise-g1.acquia-sites.com
      user: client1.01test
    
    01test-site2:
      uri: https://site2.test-client1.acsitefactory.com/
      root: /var/www/html/client1.01test/docroot
      ac-site: client1
      ac-env: 01test
      ac-realm: enterprise-g1
      01test.livedev:
        parent: '@client1.01test'
        root: /mnt/gfs/client1.01test/livedev/docroot
      host: client101test.ssh.enterprise-g1.acquia-sites.com
      user: client1.01test
    
    01dev-site1:
      uri: https://site1.dev-client1.acsitefactory.com/
      root: /var/www/html/client1.01dev/docroot
      ac-site: client1
      ac-env: 01dev
      ac-realm: enterprise-g1
      01dev.livedev:
        parent: '@client1.01dev'
        root: /mnt/gfs/client1.01dev/livedev/docroot
      host: client101dev.ssh.enterprise-g1.acquia-sites.com
      user: client1.01dev
    
    01dev-site2:
      uri: https://site2.dev-client1.acsitefactory.com/
      root: /var/www/html/client1.01dev/docroot
      ac-site: client1
      ac-env: 01dev
      ac-realm: enterprise-g1
      01dev.livedev:
        parent: '@client1.01dev'
        root: /mnt/gfs/client1.01dev/livedev/docroot
      host: client101dev.ssh.enterprise-g1.acquia-sites.com
      user: client1.01dev
    
    01dev:
      root: /var/www/html/client1.01dev/docroot
      ac-site: client1
      ac-env: 01dev
      ac-realm: enterprise-g1
      uri: client101dev.enterprise-g1.acquia-sites.com
      01dev.livedev:
        parent: '@client1.01dev'
        root: /mnt/gfs/client1.01dev/livedev/docroot
      host: client101dev.ssh.enterprise-g1.acquia-sites.com
      user: client1.01dev
    
    01devup:
      root: /var/www/html/client1.01devup/docroot
      ac-site: client1
      ac-env: 01devup
      ac-realm: enterprise-g1
      uri: client101devup.enterprise-g1.acquia-sites.com
      01devup.livedev:
        parent: '@client1.01devup'
        root: /mnt/gfs/client1.01devup/livedev/docroot
      host: client101devup.ssh.enterprise-g1.acquia-sites.com
      user: client1.01devup
    
    01live:
      root: /var/www/html/client1.01live/docroot
      ac-site: client1
      ac-env: 01live
      ac-realm: enterprise-g1
      uri: client101live.enterprise-g1.acquia-sites.com
      01live.livedev:
        parent: '@client1.01live'
        root: /mnt/gfs/client1.01live/livedev/docroot
      host: client101live.ssh.enterprise-g1.acquia-sites.com
      user: client1.01live
    
    01test:
      root: /var/www/html/client1.01test/docroot
      ac-site: client1
      ac-env: 01test
      ac-realm: enterprise-g1
      uri: client101test.enterprise-g1.acquia-sites.com
      01test.livedev:
        parent: '@client1.01test'
        root: /mnt/gfs/client1.01test/livedev/docroot
      host: client101test.ssh.enterprise-g1.acquia-sites.com
      user: client1.01test
    
    01testup:
      root: /var/www/html/client1.01testup/docroot
      ac-site: client1
      ac-env: 01testup
      ac-realm: enterprise-g1
      uri: client101testup.enterprise-g1.acquia-sites.com
      01testup.livedev:
        parent: '@client1.01testup'
        root: /mnt/gfs/client1.01testup/livedev/docroot
      host: client101testup.ssh.enterprise-g1.acquia-sites.com
      user: client1.01testup
    
    01update:
      root: /var/www/html/client1.01update/docroot
      ac-site: client1
      ac-env: 01update
      ac-realm: enterprise-g1
      uri: client101update.enterprise-g1.acquia-sites.com
      01update.livedev:
        parent: '@client1.01update'
        root: /mnt/gfs/client1.01update/livedev/docroot
      host: client101update.ssh.enterprise-g1.acquia-sites.com
      user: client1.01update
    
    dev:
      root: /var/www/html/client1.dev/docroot
      ac-site: client1
      ac-env: dev
      ac-realm: enterprise-g1
      uri: client1dev.enterprise-g1.acquia-sites.com
      dev.livedev:
        parent: '@client1.dev'
        root: /mnt/gfs/client1.dev/livedev/docroot
      host: null
      user: null
    
    prod:
      root: /var/www/html/client1.prod/docroot
      ac-site: client1
      ac-env: prod
      ac-realm: enterprise-g1
      uri: client1prod.enterprise-g1.acquia-sites.com
      prod.livedev:
        parent: '@client1.prod'
        root: /mnt/gfs/client1.prod/livedev/docroot
      host: null
      user: null
    
    test:
      root: /var/www/html/client1.test/docroot
      ac-site: client1
      ac-env: test
      ac-realm: enterprise-g1
      uri: client1test.enterprise-g1.acquia-sites.com
      test.livedev:
        parent: '@client1.test'
        root: /mnt/gfs/client1.test/livedev/docroot
      host: null
      user: null
    
  5. Write bash scripts to push and pull sites using these aliases

    The below bash script shows how to use Drush aliases in multisite automation with environment and "App ID" passed as arguments.

    #!/usr/bin/env bash
    
    # set -e
    
    project_root="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../" >/dev/null 2>&1 && pwd )"
    cd "${project_root}"
    
    multisite="${1:-main1}"
    env="${2:-01dev}"
    
    drush sql-sync @d.$env-$multisite @d.local-$multisite --create-db -y -v
    drush rsync @d.$env-$multisite:%files/ @d.local-$multisite:%files/ -y -v
    drush @d.local-$multisite cr
    # drush config:import -y
    # drush sitestudio:package:import
    drush @d.local-$multisite cohesion:rebuild
    # drush @d.local-$multisite search-api:clear
    # drush @d.local-$multisite search-api:reset-tracker
    # drush @d.local-$multisite search-api:index
    drush @d.local-$multisite uli
    
    cd -
    

    Example usage would be something like:

    bash /app/scripts/pull.sh site1 01live
    bash /app/scripts/pull.sh site2 01test
    bash /app/scripts/pull.sh site1 01dev