Configure Drupal 9 for Platform.sh


The end goal of this guide is to deploy your Drupal 9 application to a project on Platform.sh. In many ways, a project is just a collection of tools around a Git repository. A project replicates the branching structure of a repository exactly, but with one important addition: any branch can be activated to become an environment on Platform.sh. Activated environments go through Platform.sh's build and deploy phases, resulting in a fully isolated running site for each activated branch (or pull request) on that repo.

Once an environment is activated, Platform.sh will provision a cluster of containers to deploy your application, and the configuration of that cluster is controlled by three YAML files:

  • .platform/routes.yaml controls how incoming requests are routed to your application, or applications if running a multi-app setup. It also controls the built-in HTTP cache.
  • .platform/services.yaml controls what additional services are created to support your application, such as databases or search servers. Every environment has its own independent copy of every service.
  • .platform.app.yaml controls the configuration of the container where your application lives. It is the most powerful with the most options, and therefore can also get somewhat long depending on your configuration.

Each project on Platform.sh needs at least these three files, and each can be customized however you need. However, most Drupal 9 sites will have a fairly similar configuration, at least to start. More details of each are available below.

You can start by creating empty versions of each of these files in your repository:

# Create empty Platform.sh configuration files
$ touch .platform.app.yaml && mkdir -p .platform && touch .platform/routes.yaml && touch .platform/services.yaml

Now that you've added these files to your project, you can go through and configure each of them for Drupal 9 one by one in the sections below. Each section covers a particular configuration file, defines what each attribute configures, and then shows a final code snippet that includes the recommended configuration for Drupal 9 pulled from its template. Within that snippet, be sure to read each of the comments as they provide additional information and reasoning for why Drupal 9 requires those values.

Requests configuration: routes.yaml 

The routes.yaml file controls the routing and caching of all HTTP requests sent to your application cluster. Typically you will just route all incoming requests to your one application container, where your site lives, but many more elaborate configurations are possible.

The two most important parts to configure are the main route itself and its caching rules. A route can have a placeholder of {default}, which will be replaced with a branch-specific generated domain name or, in production, with your configured domain name.

The route then has an upstream, which is the name of the container that it should forward requests to. 99% of the time you want the name property in the .platform.app.yaml file.

You can (and should) enable the HTTP cache. The router includes a basic HTTP cache that will obey the HTTP cache headers produced by your application. However, by default HTTP caches will include all cookies in the cache key, which if you have any cookies at all makes the site uncacheable. Instead, the cookies key allows you to select which cookies should matter for the cache; generally that will be just the user session cookie, which for Drupal 9 is included below. However, depending on what additional modules you have installed there may be other cookies you need to add.

Finally, routes can also be HTTP redirects, either fully or partially. In this example, all www.{default} requests will be redirected to the equivalent non-www URL. You could also configure it the other way around if desired. More complex redirects are also possible.

Don't worry about unencrypted HTTP routes. All requests on Platform.sh are TLS-enabled, and we automatically redirect HTTP requests to HTTPS.

# The routes of the project.
#
# Each route describes how an incoming URL is going
# to be processed by Platform.sh.

"https://{default}/":
    type: upstream
    upstream: "app:http"
    cache:
      enabled: true

      # Base the cache on the session cookie and custom Drupal cookies. Ignore all other cookies.
      cookies: ['/^SS?ESS/', '/^Drupal.visitor/']

"https://www.{default}/":
    type: redirect
    to: "https://{default}/"

Service configuration: services.yaml 

The services.yaml file lists the pre-packaged services you need for your application to run. You pick the major version of the service, and Platform.sh updates the patch version periodically so that you always get the newest version when you deploy.

We recommend the latest MariaDB version for Drupal, although you can also use Oracle MySQL or PostgreSQL if you prefer. We also strongly recommend using Redis for Drupal caching. Drupal’s cache can be very aggressive, and keeping that data out of the database helps with both performance and disk usage. Our Drupal template comes pre-configured to use Redis for caching.

You can add other services if desired, such as Solr or Elasticsearch. You will need to configure Drupal to use those services as well once the service is enabled.

Each service entry has a name (db and cache in the example below), as well as a type that specifies the service and version to use. Note that not all services support clean version upgrades, and none support downgrades. If you want to try upgrading a service, confirm on its service page that it’s supported and test on a branch before pushing to your master branch.

If a service stores persistent data then it will also have a disk key, which specifies the amount of storage to give it, in MB.

# The services of the project.
#
# Each service listed will be deployed
# to power your Platform.sh project.

db:
    type: mariadb:10.4
    disk: 2048

cache:
    type: redis:6.0

Application container: .platform.app.yaml 

The .platform.app.yaml file is the heart of your application. It has an extensive set of options that allow you to configure nearly any aspect of your application. Most of it is explained with comments inline. You can and likely will evolve this file over time as you build out your site.

# This file describes an application. You can have multiple applications
# in the same project.
#
# See https://docs.platform.sh/configuration/app.html

# The name of this app. Must be unique within a project.
name: 'app'

# The runtime the application uses.
type: 'php:7.4'

dependencies:
    php:
        composer/composer: '^2'

runtime:
    # Enable the redis extension so Drupal can communicate with the Redis cache.
    extensions:
        - redis

# The relationships of the application with services or other applications.
#
# The left-hand side is the name of the relationship as it will be exposed
# to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand
# side is in the form `<service name>:<endpoint name>`.
relationships:
    database: 'db:mysql'
    redis: 'cache:redis'

# The size of the persistent disk of the application (in MB).
disk: 2048

# The 'mounts' describe writable, persistent filesystem mounts in the application.
mounts:
    # The default Drupal files directory.
    '/web/sites/default/files':
        source: local
        source_path: 'files'
    # Drupal gets its own dedicated tmp directory. The settings.platformsh.php
    # file will automatically configure Drupal to use this directory.
    '/tmp':
        source: local
        source_path: 'tmp'
    # Private file uploads are stored outside the web root. The settings.platformsh.php
    # file will automatically configure Drupal to use this directory.
    '/private':
        source: local
        source_path: 'private'
    # Drush needs a scratch space for its own caches.
    '/.drush':
        source: local
        source_path: 'drush'
    # Drush will try to save backups to this directory, so it must be
    # writeable even though you will almost never need to use it.
    '/drush-backups':
        source: local
        source_path: 'drush-backups'
    # Drupal Console will try to save backups to this directory, so it must be
    # writeable even though you will almost never need to use it.
    '/.console':
        source: local
        source_path: 'console'

# Configuration of the build of this application.
build:
    # Automatically run `composer install` on every build.
    flavor: composer

# The hooks executed at various points in the lifecycle of the application.
hooks:
    # The build hook runs after Composer to finish preparing up your code.
    # No services are available but the disk is writeable.
    build: |
                set -e
    # The deploy hook runs after your application has been deployed and started.
    # Code cannot be modified at this point but the database is available.
    # The site is not accepting requests while this script runs so keep it
    # fast.
    deploy: |
        set -e
        php ./drush/platformsh_generate_drush_yml.php
        cd web
        drush -y cache-rebuild
        drush -y updatedb
        drush -y config-import        

# The configuration of app when it is exposed to the web.
web:
    locations:
        # All requests not otherwise specified follow these rules.
        '/':
            # The folder from which to serve static assets, for this location.
            #
            # This is a filesystem path, relative to the application root.
            root: 'web'

            # How long to allow static assets from this location to be cached.
            #
            # Can be a time in seconds, or -1 for no caching. Times can be
            # suffixed with "s" (seconds), "m" (minutes), "h" (hours), "d"
            # (days), "w" (weeks), "M" (months, as 30 days) or "y" (years, as
            # 365 days).
            expires: 5m

            # Redirect any incoming request to Drupal's front controller.
            passthru: '/index.php'

            # Deny access to all static files, except those specifically allowed below.
            allow: false

            # Rules for specific URI patterns.
            rules:
                # Allow access to common static files.
                '\.(jpe?g|png|gif|svgz?|css|js|map|ico|bmp|eot|woff2?|otf|ttf)$':
                    allow: true
                '^/robots\.txt$':
                    allow: true
                '^/sitemap\.xml$':
                    allow: true

                # Deny direct access to configuration files.
                '^/sites/sites\.php$':
                    scripts: false
                '^/sites/[^/]+/settings.*?\.php$':
                    scripts: false

        # The files directory has its own special configuration rules.
        '/sites/default/files':
            # Allow access to all files in the public files directory.
            allow: true
            expires: 5m
            passthru: '/index.php'
            root: 'web/sites/default/files'

            # Do not execute PHP scripts from the writeable mount.
            scripts: false

            rules:
                # Provide a longer TTL (2 weeks) for aggregated CSS and JS files.
                '^/sites/default/files/(css|js)':
                    expires: 2w

crons:
    # Run Drupal's cron tasks every 19 minutes.
    drupal:
        spec: '*/19 * * * *'
        cmd: 'cd web ; drush core-cron'