Configure Laravel for Platform.sh

The end goal of this guide is to deploy your Laravel app 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 repository.

Once an environment is activated, Platform.sh provisions a cluster of containers to deploy your app. The configuration of that cluster is controlled by three YAML files:

  • .platform/routes.yaml controls how incoming requests are routed to your app, or apps in a multi-app setup. It also controls the built-in HTTP cache. If you’re only using the single default route, you don’t need this file.
  • .platform/services.yaml controls what additional services are created to support your app, such as databases or search servers. Each environment has its own independent copy of each service. If you’re not using any services, you don’t need this file.
  • .platform.app.yaml controls the configuration of the container where your app lives. It’s the most powerful configuration file with the most options. So it can get somewhat long depending on your configuration.

Each project on Platform.sh needs at least the last file and each file can be customized however you need. But most Laravel sites have a fairly similar configuration, at least to start.

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
Alternatively, you could use the `platformify` script to initialize these files. This script downloads any missing files from the official template. It doesn't affect any files you already created.
# Platformify your app and automatically download the missing configuration files
curl -fsS https://raw.githubusercontent.com/platformsh/snippets/main/src/platformify.sh | { bash /dev/fd/3 -t laravel ; } 3<&0
When run on an empty folder, the entire template can be downloaded locally.

Now that you’ve added these files to your project, you can go through and configure each of them for Laravel 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 Laravel pulled from its template. Within that snippet, be sure to read each of the comments as they provide additional information and reasoning for why Laravel requires those values.

Requests configuration: routes.yaml 

The routes.yaml file controls the routing and caching for all HTTP requests sent to your app. Typically you just route all incoming requests to your one app 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 is replaced with a branch-specific generated domain name or, in production, your configured domain name.

The route then has an upstream, which is the name of the container that it should forward requests to. Most of the time, you want your app’s name.

You can (and should) enable the HTTP cache. The router includes a basic HTTP cache that obeys the HTTP cache headers produced by your app. However, by default HTTP caches includes all cookies in the cache key. So if you have any cookies at all, you can’t cache the site. The cookies key allows you to select which cookies should matter for the cache Generally, you just want the user session cookie, which is included in the example for Laravel. You may need to add other cookies depending on what additional modules you have installed.

Routes can also be HTTP redirects, either fully or partially. In the following example, all requests to www.{default} are redirected to the equivalent URL without www. You could configure it the other way around if you want. More complex redirects are also possible.

Don’t worry about unencrypted HTTP routes. All requests on Platform.sh are TLS-enabled and HTTP requests are automatically redirected to HTTPS.

If you don’t include a routes.yaml file, a single default route is deployed. This is equivalent to the following:

.platform/routes.yaml
https://{default}/:
  type: upstream
  upstream: <APP_NAME>:http

Where <APP_NAME> is the name you’ve defined in your app configuration.

You can also create other routes as you like:

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

"https://www.{default}/":
    type: upstream
    upstream: "app:http"
"https://{default}/":
    type: redirect
    to: "https://www.{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.

You can add other services if desired, such as Solr or Elasticsearch. You need to configure to use those services once they’re 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 production branch.

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

Application container: .platform.app.yaml 

The .platform.app.yaml file is the heart of your configuration. It has an extensive set of options that allow you to configure nearly any aspect of your app. Most of it is explained with comments inline. This file changes over time as you build out your site.

# This file describes an application. You can have multiple applications
# in the same project.

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

# The type of the application to build.
type: php:8.0

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

runtime:
    extensions:
        - redis
      # - blackfire # https://docs.platform.sh/integrations/observability/blackfire.

build:
    flavor: composer

variables:
    env:
        N_PREFIX: /app/.global

# The hooks that will be performed when the package is deployed.
hooks:
    build: |
        set -e

        # install a specific NodeJS version https://github.com/platformsh/snippets/
        #   -v requested version
        #   -y install Yarn
        # curl -fsS https://raw.githubusercontent.com/platformsh/snippets/main/src/install_node.sh | { bash /dev/fd/3 -v 17.5 -y; } 3<&0

        # uncomment next line to build assets deploying
        # npm install && npm run production        

    deploy: |
        set -e
        php artisan optimize:clear

        php artisan migrate --force        

# 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"
    rediscache: "cache:redis"
    redissession: "cache:redis"

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

# The mounts that will be performed when the package is deployed.
mounts:
  "storage/app/public":
      source: local
      source_path: "public"
  "storage/framework/views":
      source: local
      source_path: "views"
  "storage/framework/sessions":
      source: local
      source_path: "sessions"
  "storage/framework/cache":
      source: local
      source_path: "cache"
  "storage/logs":
      source: local
      source_path: "logs"
  "bootstrap/cache":
      source: local
      source_path: "cache"
  "/.config":
      source: local
      source_path: "config"

# The configuration of app when it is exposed to the web.
web:
    locations:
        "/":
            root: "public"
            index:
                - index.php
            allow: true
            passthru: "/index.php"
        "/storage":
            root: "storage/app/public"
            scripts: false

crons:
    # Run Laravel's scheduler every 5 minutes, which is often as crons can run on Professional plans.
    scheduler:
        spec: '*/5 * * * *'
        cmd: 'php artisan schedule:run'
    # Run Laravel's queue worker task every 9 minutes
    queue:
        spec: '*/9 * * * *'
        # Allow the worker to run for up to 5 minutes. That prevents
        # a long-running queue from blocking a deploy for more than 5
        # minutes.
        cmd: 'php artisan queue:work --max-time=300'

# If you have an especially large queue, you may be better off using a worker.
# If so, comment out the `queue` cron entry and uncomment this instead. Note that
# Doing so requires a Medium plan or larger.
#workers:
#    queue:
#        size: S
#        commands:
#            # To minimize leakage, the queue worker will stop every hour
#            # and get restarted automatically.
#            start: |
#                php artisan queue:work --max-time=3600

source:
  operations:
    auto-update:
      command: |
                curl -fsS https://raw.githubusercontent.com/platformsh/source-operations/main/setup.sh | { bash /dev/fd/3 sop-autoupdate; } 3<&0

Now that you have Laravel configured, connect it with Laravel Bridge.