Deploying Laravel with Horizon on Upsun
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:
- Create a new Upsun project
- Generating the base Upsun configuration for our Laravel app
- Configuring our base connection to services
- Setting up our environment variables
- Do our first deploy
- Setting up Laravel Horizon to handle our queues
- 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.
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:
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:
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:
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:
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:
(\_/)
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.
┌───────────────────────────────────────────────────┐
│ 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:
$ 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:
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:
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:
# 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.
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:
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:
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:
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.
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 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:
Adding our domain name to the project
On console.upsun.com
, head to your main environment and click the top-right action: Go Live.
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:
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:
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:
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!