• Overview
    Key features
    • Observability
    • Auto-scaling
    • Multiframework
    • Security
    Frameworks
    • Django
    • Next.js
    • Drupal
    • WordPress
    • Symfony
    • Magento
    • See all frameworks
    Languages
    • PHP
    • Python
    • Node.js
    • Ruby
    • Java
    • Go
  • Industries
    • Consumer Goods
    • Media/Entertainment
    • Higher Education
    • Government
    • Ecommerce
  • Pricing
  • Featured articles
    • Switching to Platform.sh can help IT/DevOps organizations drive 219% ROI
    • Organizations, the ultimate way to manage your users and projects
  • Support
  • Docs
  • Login
  • Watch a demo
  • Free trial
Meet Upsun. The new, self-service, fully managed PaaS, powered by Platform.sh.Try it now
Blog

One codebase, many projects: Drupal multisite fleet management

drupaldevopsgithub actionsautomation
08 September, 2022
Mark Dorison
Mark Dorison
Chief Technology Officer, Partner
Chromatic

For years, Chromatic managed virtual servers on behalf of our clients. As tools like Platform.sh matured, our team realized we were spending much of our clients’ budgets on simply maintaining those servers—instead of making their sites better (which is kind of our reason for existing). 

For one of these clients, the costs of maintaining their fleet of sites/servers were really adding up. So, we decided to move their entire Drupal multisite application over to Platform.sh. On its face, this is a pretty straightforward task. But the wrinkle in this case? It’s a single codebase that powers nearly 20 sites today, with more being added each quarter; each site requires its own, siloed production environment, with slightly different resources and configurations. 

When we think about Drupal multisites, we often envision a single codebase running multiple sites in a single production environment. But in this case the environments for each of the 20 sites have always been completely siloed. So while it’s multisite from a codebase perspective, from an infrastructure perspective, these are a fleet of independent sites/projects.

When moving this codebase to Platform.sh, we had to determine how to restructure our deployments to maintain the benefits of a single codebase—without sacrificing siloed production environments. After a fair bit of discussion, we determined that the best solution was to retain our single canonical repository, but to create an individual Platform.sh project for each site. While Platform.sh source integrations work well for keeping a repository in GitHub, GitLab, or BitBucket in sync with the a single Platform.sh project, this approach wouldn’t work for our implementation for one main reason: while the application code is identical between our sites, our Platform.sh configuration files would require slight differences for different disk size requirements or custom web server rules. As it stands in August 2022, the locations of these files are quite specific, and there isn't a way to provide a different set for each site and then have Platform.sh dynamically know which one to use.

Here are our requirements:

  • One source codebase/repository in GitHub
  • Separate hosting environments for each site, or individual Platform.sh projects
  • Platform.sh configuration files that differ slightly for each site

 

Let's build artifacts!

We decided to create a deployment artifact for each site. Instead of directly connecting the source repo to the Platform.sh project repos, we would generate an artifact for each site on merge, then commit/push that to the corresponding Platform.sh project repository. In practice, the artifact is very similar to the source—if we’re looking at the number of lines of code changed. But to Platform.sh, they are some very important lines.

Using Robo to generate the artifact

At Chromatic, we use the Robo task runner extensively (see: Usher, one of our custom tools) across all of our projects, and this project was no exception. Here, we created a command that accepts a site name argument, then generates a build artifact for the specified site. The steps involved:

1. Replace a unique placeholder string in .platform.app.yaml (ex. sitename-w4BzzMcc), with the site name argument that was specified with the call to generate the artifact.
2. Insert custom web headers and other per-site configurations into .platform.app.yaml. These rules are stored in a YAML file and keyed with the same site name used above.

sitename:
  web_server_headers:
    default-src: self
    connect-src: 'self https://www.google-analytics.com https://www.googletagmanager.com'

3. Check if an overridden sitename.routes.yaml file exists. If so, copy it into place (/.platform/routes.yaml) instead of our default version.

Sample .platform.app.yaml
# Additional configuration not directly relevant to this topic has been removed
# for clarity.
---
name: sitename-w4BzzMcc
type: 'php:8.1'
runtime:
  extensions:
    - redis
variables:
  php:
    memory_limit: 256M
    date.timezone: "UTC"
relationships:
  database: 'db:mysql'
  redis: 'cacheredis:redis'
disk: 3072
mounts:
  '/web/sites/sitename-w4BzzMcc/files': 'shared:files/files'
  '/tmp': 'shared:files/tmp'
  '/private': 'shared:files/private'
build:
  flavor: composer
hooks:
  build: |
    composer robo theme:build sitename-w4BzzMcc
  deploy: |
    composer robo deploy:drupal $PLATFORM_APP_DIR sitename-w4BzzMcc
web:
  locations:
    '/sites/sitename-w4BzzMcc/files':
      allow: true
      expires: 5m
      passthru: '/index.php'
      root: 'web/sites/sitename-w4BzzMcc/files'
      scripts: false
      rules:
        '^/sites/sitename-w4BzzMcc/files/(css|js)':
          expires: 2w

 

Pushing the artifact

We do a great deal of automation with GitHub Action workflows, so using them here was a natural fit. When code is merged to our default branch in GitHub, a matrix workflow job is triggered for each version of the site that does the following to deploy the changes to each Platform.sh project automatically:

  1. Install dependencies.
  2. Generate the artifact as detailed above.
  3. Clone the project artifact repo for the given site from Platform.sh.
  4. Move our changes into the project repo working directory via rsync.
  5. Commit and push the changes back to the project repo at Platform.sh (thus deploying production in our case).

A simplified example of this workflow:

---
name: 'Platform.sh Deploy Artifact'
on:
  push:
    branches:
      - main
env:
  ARTIFACT_REPO_PATH: /tmp/artifact-repo
jobs:
  psh-deploy-artifact:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        site:
          - project1
          - project2
          - project3
        include:
          - site: project1
            clone_url: REDACTED.git
          - site: project2
            clone_url: REDACTED.git
          - site: project3
            clone_url: REDACTED.git
    steps:
      # We have omitted the usual operational steps necessary to checkout the
      # repo, set up PHP, install dependencies etc.
 
      - name: 'Apply ${{ matrix.site }} PSH config overrides'
        run: composer robo artifact:generate ${{ matrix.site }}
 
      - name: Checkout branch in artifact repo to match source repo
        working-directory: ${{ env.ARTIFACT_REPO_PATH }}
        run: git checkout ${{ env.GITHUB_REF_SLUG }} || git checkout -b ${{ env.GITHUB_REF_SLUG }}
 
      - name: 'Rsync our GH repo with modified/overridden config for ${{ matrix.site }}'
        run: rsync -zirhl --stats --delete --exclude='/.git' --filter="dir-merge,- .gitignore" "$GITHUB_WORKSPACE/" ${{ env.ARTIFACT_REPO_PATH }}
 
      - name: Commit and push PSH repo back to PSH as newly built artifact
        working-directory: '${{ env.ARTIFACT_REPO_PATH }}'
        run: |
          if [[ $(git status --porcelain) ]]; then
            echo "Changes found. Making git commit."
            git config --local user.email "github-actions[bot]@users.noreply.github.com"
            git config --local user.name "github-actions[bot]"
            git add -A
            git commit -m "Artifact built from $GITHUB_SHA by GitHub Action workflow."
          else
            echo "No changes found. Skipping commit."
          fi
          git push -u origin "$GITHUB_REF_SLUG"

 

Wrap-up

Our approach has enabled us to retain all of the benefits of a single codebase powering dozens of sites and all of the advantages Platform.sh grants us. When we’re pushing out new features on the application or simply updating Drupal core, we have an efficient, repeatable, and automated deployment. It saves us and our clients time and money and, ultimately, lets us deliver more value.

Interested in more technical insights from the Chromatic team? Explore their blog.

Mark Dorison is Chromatic’s chief technical officer and partner. Away from Chromatic, Mark is an avid traveler, cyclist, amateur TV critic, and splits a game design studio 50/50 with his daughter Kai.

Feel free to reach out to him via Twitter.

Get the latest Platform.sh news and resources
Subscribe

Related Content

CTO insights: lower costs, maintain high quality apps

CTO insights: lower costs, maintain high quality apps

Company
AboutSecurity and complianceTrust CenterCareersPressContact us
Thank you for subscribing!
  •  
Field required
Leader Winter 2023
System StatusPrivacyTerms of ServiceImpressumWCAG ComplianceAcceptable Use PolicyManage your cookie preferencesReport a security issue
© 2024 Platform.sh. All rights reserved.
Supported by Horizon 2020's SME Instrument - European Commission 🇪🇺