Deploying Laravel with Horizon on Upsun

Deploying Laravel with Horizon on Upsun

February 28, 2024· Guillaume Moigneu
Guillaume Moigneu
·Reading time: 11 minutes

We will rely on the terminal and the Upsun CLI to deploy our application. Our end-goal is to deploy our application with a real production domain at the end.

Let’s review the steps we will have to go through:

  1. Create a new Upsun project
  2. Generating the base Upsun configuration for our Laravel app
  3. Configuring our base connection to services
  4. Setting up our environment variables
  5. Do our first deploy
  6. Setting up Laravel Horizon to handle our queues
  7. Configuring our domain

Create a new Upsun project

Launch your terminal and make sure you have the Upsun CLI available. You can refer to this page on the documentation to install the CLI tool.

cd into your Laravel application base directory and start by logging in with upsun login. Go through the web-based authentication process.

Terminal
upsun login
Opened URL: http://127.0.0.1:5001
Please use the browser to log in.

Help:
  Leave this command running during login.
  If you need to quit, use Ctrl+C.

Login information received. Verifying...
You are logged in.

Generating SSH certificate...
A new SSH certificate has been generated.
It will be automatically refreshed when necessary.

Username: ---
Email address: guillaume.moigneu@platform.sh

Once done, we will create a new empty project with upsun project:create. Follow the prompts:

Terminal
upsun project:create
Creating a project under the organization Nls (nls)

* Project title (--title)
Default: Untitled Project
> Wooply.co
Select the best region for hosting your project. Note that the regions generating less than 100 gC02eq/kWh are considered green and will benefit from an automated discount on resources.
* Region (--region)
The region where the project will be hosted.
Get a 3% discount on resources for regions with a carbon intensity of less than 100 gCO2eq/kWh.
  [au.platform.sh  ] Sydney, Australia (AWS)  [545 gC02eq/kWh]
  [au-2.platform.sh] Sydney, Australia (AZURE)  [545 gC02eq/kWh]
  [ca-1.platform.sh] Montreal, Canada (AWS)  [31 gC02eq/kWh]
  [ch-1.platform.sh] Zurich, Switzerland (GCP)  [91 gC02eq/kWh]
  [de-2.platform.sh] Frankfurt, Germany (GCP)  [416 gC02eq/kWh]
  [eu.platform.sh  ] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-2.platform.sh] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-4.platform.sh] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-5.platform.sh] Stockholm, Sweden (AWS)  [23 gC02eq/kWh]
  [fr-3.platform.sh] Gravelines, France (OVH)  [59 gC02eq/kWh]
  [fr-4.platform.sh] Paris, France (AZURE)  [59 gC02eq/kWh]
  [uk-1.platform.sh] London, United Kingdom (GCP)  [200 gC02eq/kWh]
  [us.platform.sh  ] Washington, United States (AWS)  [396 gC02eq/kWh]
  [us-2.platform.sh] Washington, United States (AWS)  [396 gC02eq/kWh]
  [us-3.platform.sh] Moses Lake, United States (AZURE)  [56 gC02eq/kWh]
  [us-4.platform.sh] Charleston, United States (GCP)  [647 gC02eq/kWh]
> us-3.platform.sh

The CLI will auto-detect your git repository and will add the Upsun endpoint as a new remote for your repository:

Terminal
Default branch (--default-branch)
The default Git branch name for the project (the production environment)
Default: main
> main

Git repository detected: /Users/nls/projects/woop.ly/wooply.co
Set the new project Wooply.co as the remote for this repository? [Y/n]

The Upsun platform will now take a minute to provision your project:

Terminal
The Upsun Bot is activating your project

      ▄     ▄
      ▄█▄▄▄█▄
    ▄██▄███▄██▄
    █ █▀▀▀▀▀█ █
       ▀▀ ▀▀

The project is now ready!
snacckbxx5cee

  Region: us-3.platform.sh
  Project ID: snacckbxx5cee
  Project title: Wooply.co
  URL: https://console.upsun.com/01hd1s2bjt44369c0923a70g44/snacckbxx5cee
  Git URL: snacckbxx5cee@git.us-3.platform.sh:snacckbxx5cee.git

Setting the remote project for this repository to: Wooply.co (snacckbxx5cee)

Let’s move on to creating the configuration.

Generating the base Upsun configuration for our Laravel app

The Upsun CLI comes bundled with our nifty upsun ify command that will auto-generate a base configuration based on the runtime and framework you are using.

Let’s launch it in our Laravel directory:

Terminal
upsun ify
Welcome to Upsun!
Let's get started with a few questions.

We need to know a bit more about your project. This will only take a minute!

✓ Detected stack: Laravel
✓ Detected runtime: PHP
✓ Detected dependency managers: Composer, Npm
Tell us your project's application name: [wooply]

upsun ify detects a Laravel app and automatically setup the configuration for PHP, composer and npm. We now need to tell Upsun which services we are running. Just select in the list the services the app relies on. In our case, we will elect PostgreSQL and Redis:

Terminal
                       (\_/)
We're almost done...  =(^.^)=

Last but not least, unless you're creating a static website, your project uses services. Let's define them:

Select all the services you are using:
Use arrows to move, space to select, type to filter
> [ ]  MariaDB
  [ ]  MySQL
  [x]  PostgreSQL
  [x]  Redis
  [ ]  Redis Persistent
  [ ]  Memcached
  [ ]  OpenSearch

If your application needs to handle local files on the filesystem, it is recommended to add Network Storage as this will allow multiple instances of the application or a worker (horizon) to access the files as well.

Terminal
┌───────────────────────────────────────────────────┐
│   CONGRATULATIONS!                                │
│                                                   │
│   We have created the following files for your:   │
│     - .environment                                │
│     - .upsun/config.yaml                          │
│                                                   │
│   We're jumping for joy! ⍢                        │
└───────────────────────────────────────────────────┘
         │ /
         │/
  (\ /)
  ( . .)
  o (_(")(")

You can now deploy your application to Upsun!

To do so, commit your files and deploy your application using the Upsun CLI:

Terminal
  $ git add .
  $ git commit -m 'Add Upsun configuration files'
  $ upsun project:set-remote
  $ upsun push

The configuration is now generated. Let’s follow the directions and commit it:

Terminal
git add .
git commit -m 'Add Upsun configuration files'
[main 953816a] Add Upsun configuration files
 2 files changed, 193 insertions(+)
 create mode 100644 .environment
 create mode 100644 .upsun/config.yaml

Configuring the application to access the services

In order for our Laravel application to connect to our services, we need to inject the proper configuration.

First add the redis and pdo_pgsql extensions to .upsun/config.yaml if needed:

applications:  
  YOUR_APP_NAME:
    [...]
    runtime:  
      extensions:  
        - redis
        - pdo_pgsql
 [...]

Add the change to the git index and commit:

Terminal
git add .upsun/config.yaml
git commit -m 'Add redis php extension'

We will now look at the .environment file that auto-generates some variables for our app. The upsun ify command should have added the necessary variable declarations for Laravel. This file maps default Upsun environment variables to the ones needed by Laravel. You can adjust this file if needed or add additional mappings like we have done here with the email settings:

Terminal
# Set database environment variables  
export DB_HOST="$POSTGRESQL_HOST"  
export DB_PORT="$POSTGRESQL_PORT"  
export DB_PATH="$POSTGRESQL_PATH"  
export DB_USERNAME="$POSTGRESQL_USERNAME"  
export DB_PASSWORD="$POSTGRESQL_PASSWORD"  
export DB_SCHEME="$POSTGRESQL_SCHEME"  
export DATABASE_URL="${DB_SCHEME}://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_PATH}"  
  
# Set Laravel-specific environment variables  
export DB_CONNECTION="$DB_SCHEME"  
export DB_DATABASE="$DB_PATH"  
  
# Set Cache environment variables  
export CACHE_HOST="$REDIS_HOST"  
export CACHE_PORT="$REDIS_PORT"  
export CACHE_SCHEME="$REDIS_SCHEME"  
export CACHE_URL="${CACHE_SCHEME}://${CACHE_HOST}:${CACHE_PORT}"  
  
# Set Redis environment variables  
export REDIS_URL="$CACHE_URL"

# Email
export MAIL_MAILER="smtp"  
export MAIL_HOST="${PLATFORM_SMTP_HOST}"  
export MAIL_PORT="25"

Last step before deploying our app, we need to inject our remaining configuration through dynamic environment variables.

Setting dynamic environment variables

Open your local .env file. As Upsun already handle the specific services (database, cache, queues) settings, we only need to setup the global configuration. Here are the ones we need for our Wooply project. Note that we use the env: prefix to expose the variable to the container and the app directly.

The MODEL_CACHE_ENABLED and LPL_ variables that we added here are requirements for additional packages and not default for Laravel. Variable can also be overriden per environment if you set their level to environment instead of project.

Terminal
upsun variable:create --level project --prefix env --name APP_NAME --value="Wooply"
upsun variable:create --level environment --environment main --prefix env --name APP_ENV --value="production"
upsun variable:create --level environment --environment main --prefix env --name APP_DEBUG --value="false"

upsun variable:create --level project --prefix env --name LOG_CHANNEL --value="stack"
upsun variable:create --level project --prefix env --name LOG_DEPRECATIONS_CHANNEL --value="null"
upsun variable:create --level environment --environment main --prefix env --name LOG_LEVEL --value="error"

upsun variable:create --level environment --environment main --prefix env --name MODEL_CACHE_ENABLED --value="true"

upsun variable:create --level project --prefix env --name LPL_USER_MODEL --value="App\Models\User"
upsun variable:create --level project --prefix env --name LPL_REMEMBER_LOGIN --value="true"
upsun variable:create --level project --prefix env --name LPL_LOGIN_ROUTE --value="/magic-login"
upsun variable:create --level project --prefix env --name LPL_LOGIN_ROUTE_NAME --value="magic-login"
upsun variable:create --level project --prefix env --name LPL_LOGIN_ROUTE_EXPIRES --value="30"
upsun variable:create --level project --prefix env --name LPL_REDIRECT_ON_LOGIN --value="/dashboard"
upsun variable:create --level project --prefix env --name LPL_USER_GUARD --value="web"
upsun variable:create --level project --prefix env --name LPL_USE_ONCE --value="false"
upsun variable:create --level project --prefix env --name LPL_INVALID_SIGNATURE_MESSAGE --value=""Expired or Invalid Link""

upsun variable:create --level project --prefix env --name BROADCAST_DRIVER --value="redis"
upsun variable:create --level project --prefix env --name CACHE_DRIVER --value="redis"
upsun variable:create --level project --prefix env --name FILESYSTEM_DISK --value="local"
upsun variable:create --level project --prefix env --name QUEUE_CONNECTION --value="redis"
upsun variable:create --level project --prefix env --name SESSION_DRIVER --value="redis"
upsun variable:create --level project --prefix env --name SESSION_LIFETIME --value="120"

upsun variable:create --level project --prefix env --name MAIL_FROM_ADDRESS --value=""hello@wooply.co""
upsun variable:create --level project --prefix env --name MAIL_FROM_NAME --value="Wooply"

upsun variable:create --level project --prefix env --name VITE_APP_NAME --value="Wooply"

Everything is now ready to push our code.

If you are using private packages like Laravel Nova, you will need to inject the composer configuration as a variable. It can be done with the following:

Terminal
upsun variable:create --level project --name env:COMPOSER_AUTH \
  --json true --visible-runtime false --sensitive true --visible-build true \
  --value '{"http-basic": {"nova.laravel.com": {"username": "USERNAME", "password": "PASSWORD"}}}'

First deploy

Still in the base directory, you can now use upsun push to push your commits to the Upsun remote:

Terminal
upsun push
Selected project: Wooply.co (snacckbxx5cee)

Pushing HEAD to the environment main (type: production).

Are you sure you want to continue? [Y/n]

  Enumerating objects: 580, done.
  Counting objects: 100% (580/580), done.
  Delta compression using up to 10 threads
  Compressing objects: 100% (544/544), done.
  Writing objects: 100% (580/580), 4.60 MiB | 3.45 MiB/s, done.
  Total 580 (delta 191), reused 0 (delta 0), pack-reused 0

  Validating submodules

  Validating configuration files

  Processing activity: Guillaume Moigneu pushed to Main
      Found 13 commits

      Building application 'wooply' (runtime type: php:8.2, tree: a84c514)

The Upsun deployment pipeline will start building your project. We will go into this later in more details. If you encountered an error during that process, update the configuration or variables and do a upsun redeploy to restart the process.

The deploy phase is now complete and prints out your project URLs. Not that they are auto-generated for now as we don’t have added any domain yet:

Terminal
Opening environment
        Environment configuration
          wooply (type: php:8.2, cpu: 0.5, memory: 224, disk: 512)
          postgresql (type: postgresql:15, cpu: 0.5, memory: 1408, disk: 512)
          redis (type: redis:7.0, cpu: 0.5, memory: 1088)

        Environment routes
          http://main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/ redirects to https://main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/
          http://www.main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/ redirects to https://www.main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/
          https://main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/ is served by application `wooply`
          https://www.main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/ redirects to https://main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/

Our project has also been deployed with default minimal resources for our containers and is now available on https://main-bvxea6i-snacckbxx5cee.us-3.platformsh.site/. Database migrations are run automatically on each deploy to ensure your code matches the database schema. You can see what exactly happens in the .upsun/config.yaml inside the build and deploy hooks.

Now that our application is working, let’s configure our Horizon worker.

Setting up Horizon workers and queues

If needed, add laravel/horizon to your local project and follow the documentation on the Horizon page.

Terminal
composer require laravel/horizon
php artisan horizon:install

We are now going to add a separate application on our project. Open .upsun/config.yaml and add the following:

applications:
[...]
  wooply:
  [...]
  workers:  
    horizon:  
     commands:  
         start: |  
          php artisan horizon

This way, a new container will be started automatically and will spawn the Horizon process.

If you have restricted access to horizon in your HorizonServiceProvider.php, login to your application through the web. Once done, head to /horizon and you should be able to access your horizon dashboard:

Horizon Dashboard

Horizon will handle jobs that are populated by the queue. If you need to customize how Horizon works (queues, processes, …), please refer to the official Laravel Horizon documentation.

As any other container, the resources of the Horizon container can be customized. Head to the Configure resources panel of the Upsun console and change the resources as needed:

Scaling resources

Adding our domain name to the project

On console.upsun.com , head to your main environment and click the top-right action: Go Live.

Add a domain

Copy the provided CNAME and head to your DNS provider management panel. I’m using CloudFlare here but any will do.

Add the CNAME record to your DNS zone:

Add DNS Record

Note: If your DNS provider does not allow CNAME on @ records, take a look at Upsun documentation on alternatives.

Once done, add the domain to your project:

Terminal
upsun domain:add wooply.co
Use the --environment option (and optionally --attach) to add a domain to a non-production environment.

Selected project: Wooply.co (snacckbxx5cee)
Selected environment: main (type: production)

Adding the domain: wooply.co

Are you sure you want to continue? [Y/n]

Waiting for the activity: [dmfjsbev7dbcw] Guillaume Moigneu added domain wooply.co

Building application 'wooply' (runtime type: php:8.2, tree: 30b62f6)
  Reusing existing build for this tree ID

Configuring resources

Provisioning certificates
  Validating 2 new domains
  Certificates
  - certificate 32dc903: expiring on 2024-05-28 15:02:43+00:00, covering wooply.co

The Upsun platform will add the configuration and automatically provision Let’s Encrypt certificates for all your routes:

Terminal
Opening environment
  Environment configuration
    wooply (type: php:8.2, cpu: 0.5, memory: 224, disk: 512)
    wooply--horizon (type: php:8.2, cpu: 0.5, memory: 224, disk: 0)
    postgresql (type: postgresql:15, cpu: 0.5, memory: 1408, disk: 512)
    redis (type: redis:7.0, cpu: 0.5, memory: 1088)

  Environment routes
    http://wooply.co/ redirects to https://wooply.co/
    http://www.wooply.co/ redirects to https://www.wooply.co/
    https://wooply.co/ is served by application `wooply`
    https://www.wooply.co/ redirects to https://wooply.co/

Our Laravel application is now live on its own domain!

Last updated on