Deploy your Laravel app from scratch

Deploy using Laravel Forge

Episode 6
3 years ago
13 min read

In this series, we’ve seen how to deploy a Laravel application from scratch by creating our server manually. Whilst it’s good to know how to do it ourselves to understand the mechanics of our server, it can be a pain to maintain in the long run.

That’s why SaaS applications such as Laravel Forge and Ploi exist. They provide an abstraction layer between you and your server by automating its provisioning, its maintenance and by allowing you to configure it directly inside a user interface.

This article focuses on creating a server using Laravel Forge and deploying to it using Deployer. The next one will focus on Ploi.

Preparations

Before we start, I’m going to assume you already have a Laravel Forge account and that you’ve configured it appropriately.

If you’re going to follow along, make sure the following points are configured.

  • Add a Digital Ocean provider by generating an API key first. We’ll use Digital Ocean again here to create our server but feel free to choose any of the alternative providers.
  • Add your SSH key to your account. This will ensure your key is automatically added to every server created. Alternatively, make sure you add it to your server configurations after creating it.
  • Add a Git provider to your account. Even though we’ll be deploying using Deployer that already knows your repository URL, we’ll need to provide our repository when creating a site in order to unlock part of the UI.

All of these configurations can be found on your account page on Laravel Forge.

Screenshot of the "Account" page on Forge

Create your server on Forge

Alright, let’s get started. We’ll create a new server directly on the Laravel Forge interface.

Select the server provider of your choice — in our case, we’ll use Digital Ocean — select your credentials, select the “App Server” type and fill the rest of the form however you like.

Screenshot of the page to create a new server on Laravel Forge.

Notice how you can select the PHP version of your choice before creating the server. Additionally, you’ll be able to upgrade or downgrade PHP versions later on with only one click. That’s much easier than having to do it ourselves as we did in the second episode of this series.

Don’t forget to provide a database name and — when you’re done — click “Create Server”.

As soon as you do, you’ll receive two important passwords that you’ll see only once so make sure to save them in an app like 1Password or something.

Screenshot of the "Server Credentials" modal on Laravel Forge providing us with our "Sudo Password" and our "Database Password".

  • The first one is the password you’ll be asked to enter whenever you enter a sudo command on your server.
  • The second one is the database password of the forge user. We’ll need this to access our production database later on.

When they’re all saved, click “Close” and you should see your server listed below with the badge “Provisioning”. This means your server is being created on Digital Ocean and Laravel Forge is running a bunch of scripts on it to install everything we need for our Laravel applications.

If you click on your server you should see a detailed list of what it’s doing and what’s left to do before your server is ready.

Screenshot the server's page on Laravel Forge whilst it is provisioning. It shows a long list of tasks that are either "Finished", "Running" or "Pending".

Now this may take a little while so, whilst we’re waiting, let’s point our domain name to our new server.

Configure your domain

As we’ve seen in episode 2, we need to add a record in our DNS configurations for our domain name to point to the IP address of our server.

In this tutorial, we’ve already assigned jollygood.app to the server we manually created in episode 2. Thus, I am going to use the subdomain forge.jollygood.app to point to our new server created by Forge. Of course, feel free to use any domains and/or subdomains for your new server.

Screenshot of the Digital Ocean "Domains" page. It shows a new record being created with the "forge" subdomain, pointing to the new server we created directly on Forge.

Once that’s done, it may take a few minutes or even hours for the changes to be live so it’s better to do this as soon as we’ve got the IP address of our server.

With any luck, the DNS changes should be live by the time the server has finished being configured on Laravel Forge.

Add a site to your server

Once our server has been successfully provisioned, we should be able to add sites to it.

First, we enter the domain of our application that matches the DNS record we’ve just created on Digital Ocean — in our case forge.jollygood.app. If we added more than one DNS records, feel free to add the others inside “Aliases”.

Next — and that’s important — replace the “Web Directory” field with /current/public. This is because, as we’ve seen in episode 4, when deploying with Deployer, a subfolder named current will be created pointing to the latest stable release. This will ensure our Nginx configuration points to the right directory.

Finally, I’m not going to tick “Create Database” since we’ve already created one when creating our server. However, if this is the second site you create on the same server, you might want to tick it so your second database is ready for you.

Screenshot of the page to create a new site inside a server on Laravel Forge. The field "Web Directory" is highlighted and contains "/current/public/".

After clicking on “Add Site”, you should be able to see your new site listed below. If you click on it, you should see the following page.

Screenshot of the site page after creating it on Laravel Forge. It shows three big buttons: "Git Repository", "WordPress" and "phpMyAdmin". The "Git Repository" button is highlighted.

Laravel Forge is asking us to provide a Git repository so it can clone it inside the server for us.

Technically, we’ve got no need for that since we’ll be deploying using Deployer who already knows our repository URL. However, if we don’t, the user interface for our new site will be locked in this state which is not very helpful to maintain it.

Thus, we’re going to play the game and add our Git repository even though we’ll re-deploy using Deployer in a minute.

Choose the Git provider of your choice and enter your repository details. There’s also no need to tick “Install Composer Dependencies” since we’re going to re-deploy in a minute.

Screenshot of the site page after selecting "Git Repository" on Laravel Forge.

Next, there’s a little adjustment we need to make to our Nginx configuration file. If you remember, in episode 2, we mentioned that the SCRIPT_FILENAME and DOCUMENT_ROOT FastCGI parameters had to be overridden to use the real absolute path to avoid symlink paths being incorrectly cached. Since Laravel Forge does not expect us to use Deployer by default, its Nginx configuration does not account for that. But that’s fine we can update this directly inside the UI.

At the very bottom of your site’s page, click on “Files” then “Edit Nginx Configuration” and add the following lines after include fastcgi_params.

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Screenshot of the "Edit Nginx Configuration" modal on Laravel Forge.

After that, make sure to restart Nginx to apply your changes.

Screenshot showing where the "Restart Nginx" button is on Laravel Forge. At the very end of your server page, click on the "Restart" button which will open a dropdown containing the "Restart Nginx" button.

If you’re planning on using Deployer for a lot of sites in the future, you may also create a new Nginx template that will be used instead of the default one. To do that, go to your server’s page, click on “Nginx Templates” on the sidebar and create a new template by adding the two lines above.

Screenshot of the "Create Nginx Template" page on Laravel Forge.

Finally, let’s make sure our domain is available via HTTPS. Laravel Forge makes this super easy for us. On your site’s page, click on “SSL” on the sidebar and select “LetsEncrypt”.

Then make sure you enter the right domains and click “Obtain Certificate”. And that’s it.

Screenshot of the "New Certificate" page on Laravel Forge. The "LetsEncrypt" button is highlighted.

A Laravel Forge friendly deploy.yaml

Okay, now that our server and our site are ready, let’s make sure we can deploy using Deployer.

For this article, I will use the same configuration file we ended up with after episode 4. However, I’m going to update the host configurations slightly so it works with Laravel Forge.

  • By default, we can access servers created on Laravel Forge using the forge user so we’ll use this as remote_user.
  • Then, we’ll use forge.jollygood.app as the hostname since we’ve created a DNS record that points to the IP address of our server.
  • Finally, Laravel Forge installs our sites in the home directory of the forge user and uses the site’s domain to name the site’s folder. So we’ll use the same convention here and deploy to /home/forge/forge.jollygood.app which can be simplified to ~/{{hostname}}.

Additionally, we need to make sure the php_fpm_version matches the PHP version of our server.

Thus, we end up with the following deploy.yaml file.

import:
  - recipe/laravel.php
  - contrib/php-fpm.php
  - contrib/npm.php

config:
  application: 'blog-jollygood'
  repository: '[email protected]:lorisleiva/blog-jollygood.git'
  php_fpm_version: '8.0'

hosts:
  prod:
    remote_user: forge
    hostname: 'forge.jollygood.app'
    deploy_path: '~/{{hostname}}'

tasks:
  deploy:
    - deploy:prepare
    - deploy:vendors
    - artisan:storage:link
    - artisan:view:cache
    - artisan:config:cache
    - artisan:migrate
    - npm:install
    - npm:run:prod
    - deploy:publish
    - php-fpm:reload
  npm:run:prod:
    - run: 'cd {{release_or_current_path}} && npm run prod'

after:
  deploy:failed: deploy:unlock

Deploy once

Okay now we should be ready to deploy but before we do let’s delete the folder generated by Laravel Forge when we created our site.

Deployer will be generating a different folder structure with a releases folder and a current symlink. If we don’t delete the existing folder, we’ll end up with a strange fusion of Deployer and a traditional deployment.

Let’s SSH into our server by running dep ssh, then go to the home directory ~ and run rm -rf forge.jollygood.app or whatever your domain is.

Screenshot of the terminal output of "dep ssh", "ls", "rm -rf forge.jollygood.app", "ls".

Alright, now we’re finally ready to deploy. Simply exit the server and run dep deploy. You should see the following familiar console output.

Screenshot of the terminal output of "dep deploy". All tasks ran successfully except for the "artisan:migrate" task showing the warning: "Your .env file is empty! Skipping...".

If you remember, the artisan:migrate did not run because our .env file has been generated in Deployer’s shared folder but it is empty. So let’s fix this.

First, we’ll copy the .env.example file and generate an application key randomly.

# SSH into your server.
dep ssh

# Prepare the .env file.
cp .env.example .env
php artisan key:generate

# Exit your server.
exit

Now, if you remember, in episode 4, we had to edit our .env file directly inside our server using vim.

We can still do that, but Laravel Forge provides a nice interface for us to update our .env directly from their application. Simply go to our site’s page and click on “Environment” on the sidebar. Make sure to update your production variables appropriately and use the database password provided earlier when creating the server.

Screenshot of the "Environment" page on Laravel Forge.

Deploy twice

Now that our production environment is ready, let’s deploy a second time to ensure our database is migrated. Simply run dep deploy and with any luck, you should see the following output.

Screenshot of the terminal output of "dep deploy". This time, all tasks ran successfully.

And that’s it! You should now be able to see your application live if you visit its URL. 🥳

Screenshot of a browser visiting the page at forge.jollygood.app. It shows the boilerplate of a newly created Laravel application.

Update env variables

Okay, we’ve successfully deployed our application using Laravel Forge and Deployer but there are still a few points I’d like to mention.

The first point is that, once your application is deployed, you’ll likely want to update some environment variables from time to time

Since Laravel Forge has a dedicated page to do so, it can be easy to forget that our configuration files are cached — due to the artisan:config:cache task we added to our deployment flow.

That means, whenever you update your .env file, the changes won’t be live until the next deployment.

That being said, if you want to regenerate the configuration cache without having to redeploy the application, you may do that by running php artisan config:cache on your server.

A nice touch from Laravel Forge is that it allows you to run such commands directly from the UI. On your site’s page, click on “Commands” on the sidebar and enter your command right here.

However, since it will run your command in the parent directory of the current release, make sure to enter php current/artisan config:cache so it runs in the right directory.

Screenshot of the "Commands" page on Laravel Forge. It shows a "Command" field containing the following value: "php current/artisan config:cache".

Delete the default server

The second point is that Laravel Forge generates a “default” site for every new server created. That site does not have a domain associated with it but is accessible if you enter the IP address of your server in your navigator.

This is nice for making sure your server is up and running after creating it but not very secure in the long run so it is recommended to delete it once your real sites are ready.

You may do this directly in the interface by clicking on “Delete Site” at the bottom of the “default” page.

Screenshot of the "default" site page on Laravel Forge. The "Delete Site" button at the end of the page is highlighted.

Bonus: Deploy via the deploy script

Finally, my last point is optional but can be useful if you’d like to trigger a Deployer deployment directly from the Laravel Forge interface.

You might think that all you need to do for it to work is to add dep deploy inside your Laravel Forge deploy script and be done with it.

Unfortunately, it’s not that simple. When running dep deploy from our local machine, it works by connecting via SSH to all our hosts.

Therefore, whoever ends up running dep deploy needs to be able to access all hosts configured inside our deploy.yaml file via SSH.

That means, if we add this to our Laravel Forge deploy script, our server suddenly needs to access itself via SSH and any other server we might have.

I wouldn’t recommend doing this if you have more than one server configured in your deploy.yaml file since it becomes a messy deployment process where each server has to know about each other. However, if you’ve got only one server and like the convenience of deploying at the click of a button, then that could be useful. Thus, from now on, I’m going to assume we’ve only got one server to deploy to.

Alright, things are going to get a bit weird so bear with me.

The first thing to do for the server to connect via SSH to itself is to add the server’s SSH key to its own list of trusted SSH keys.

So, connect to your server — dep ssh — and copy its public key — cat ~/.ssh/id_rsa.pub.

Screenshot of the terminal output for "dep ssh" and "cat ~/.ssh/id_rsa.pub".

Then, on the Laravel Forge interface, paste that public key to the server’s trusted SSH keys. I usually call it “Self”.

Screenshot of the "SSH Keys" page on Laravel Forge. It shows a form to add a new key where the name is "Self", the user is "forge" and the public key is what was copied from the previous command.

Now, if we try to run dep deploy inside the server it still won’t work because the hostname has not yet been added to the server’s trusted hosts.

The simplest way to do this is to manually run dep ssh inside the server so we can say “yes” to the question “Do you trust this host?”. Concretely, that’s how we do it:

  • Run dep ssh once to connect to your server from your local machine. You should end up in your current release.
  • Run vendor/bin/dep ssh again to connect to your server from your server. Inception much.
  • Say yes to add the hostname to the server’s trusted hosts.

Screenshot of the terminal output for "dep ssh" followed by "vendor/bin/dep ssh".

Don’t forget to double exit your server now.

Screenshot of the terminal output for "exit" and "exit" again.

Okay, now we can edit our deploy script so that it immediately delegates to Deployer via dep deploy.

A few notes though.

  • First, make sure you update the cd line to point to the current release.
  • Next, use vendor/bin/dep instead of dep since Laravel Forge doesn’t add ./vendor/bin into our PATH variable for us.
  • Finally, manually provide the branch you’d like to deploy using -o branch=my-branch. This is because deployed releases don’t have git initialised inside them. Instead, Deployer uses a cached repository inside the .dep folder. That means Deployer cannot guess the branch we want to deploy so we have to provide it explicitly.

You should end up with the following deploy script.

cd /home/forge/forge.jollygood.app/current
vendor/bin/dep deploy -o branch=main

Screenshot of the site page with the updated Deploy Script.

And now, finally, if we click “Deploy Now” you should see the following deployment log indicating that we’ve successfully deployed using Deployer.

Screenshot of the "Deployment Log" modal showing a successful Deployer deployment.

Conclusion

Alright, I hope this article was useful for Laravel Forge users and also for those who are looking for a solution to help them create and maintain servers.

If that bonus section above felt a bit overkill just to deploy using a button, I can understand. Feel free to discard it if you’re not comfortable with it but hopefully it thought you something new that you can do with Laravel Forge and Deployer.

As usual, you can find the deploy.yaml file updated for this episode on GitHub by click on the link below.

See deploy.yaml on GitHub

As an alternative to Laravel Forge, you might also want to consider Ploi which I will talk about in the next episode.

I have no personal preference between the two and so I’m actually a customer of both because I'm a very indecisive person. 😅 Hopefully, these two articles will help you decide on which one suits you best.

After these two episodes about Laravel Forge and Ploi, I will provide a complete checklist of this entire series as a gift for my wholesome sponsors. This will be the perfect article to come back to when you’re ready to get your hands dirty and want a quick list of things to do to deploy your Laravel app from scratch.

← Previous episode
Create your own Deployer recipes
Next episode →
Deploy using Ploi

Discussions

Author avatar
Andrea
3 years ago

Hi Loris and thanks for this fantastic article!

I tried to follow the same procedure with an Octane site on Forge but, when deploying a new release, only the code from the previous release is used.

With dep ssh I can see that current points to the new release correctly.

Because I'm on an Octane site, I couldn't follow the step of the FastCGI parameters, as a proxy_pass is used instead. Is there an alternative solution for that?

The last 2 tasks of my deploy script are:

  • cd {{release_or_current_path}} && php artisan octane:reload
  • php-fpm:reload so, in theory, the Octane server and OPCache should read the updated code.

What am I missing? Thanks again for your great contribution to FOSS!

💖 1

Discussion

Deploy using Laravel Forge
Author avatar
Andrea
3 years ago

Hi Loris and thanks for this fantastic article!

I tried to follow the same procedure with an Octane site on Forge but, when deploying a new release, only the code from the previous release is used.

With dep ssh I can see that current points to the new release correctly.

Because I'm on an Octane site, I couldn't follow the step of the FastCGI parameters, as a proxy_pass is used instead. Is there an alternative solution for that?

The last 2 tasks of my deploy script are:

  • cd {{release_or_current_path}} && php artisan octane:reload
  • php-fpm:reload so, in theory, the Octane server and OPCache should read the updated code.

What am I missing? Thanks again for your great contribution to FOSS!

💖 1
Author avatar
Loris Leiva
3 years ago

Hi Andra,

Thank you for the kind words.

I'm really sorry but I haven't tried Octane yet so I'm not sure how to fix this.

I see you've already created a discussion on the Deployer repo which is probably going to yield more useful answers. I'll be sure to keep an eye.

💖 1
Author avatar
Andrea
3 years ago

Hi Loris, thanks for your help and for taking the time to reply :)

💖 1

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Gaurav Makhecha
3 years ago

Thank you for the bonus section. Being able to deploy from Forge is certainly helpful.

Question: I remember you mentioned about local() option of Deployer Tasks in one of the previous episodes. Isn't it possible to use that feature when deploying from Forge?

💖 0

Discussion

Deploy using Laravel Forge
Author avatar
Gaurav Makhecha
3 years ago

Thank you for the bonus section. Being able to deploy from Forge is certainly helpful.

Question: I remember you mentioned about local() option of Deployer Tasks in one of the previous episodes. Isn't it possible to use that feature when deploying from Forge?

💖 0
Author avatar
Loris Leiva
3 years ago

Hi Gaurav 👋

Sadly the local() method only affect the task you're modifying and not the whole deployment process. If you did add local() to all your tasks then you'd only be able to deploy locally and never from your local machine for instance.

On the previous version of Deployer, there was a way to add a "localhost" host on your deploy.php file but I remember trying it and not being able to use it to deploy on Forge.

That being said I've done a quick research on the GitHub repo and it looks like there is a localhost method available on the newest version of Deployer but it's not yet documented so there could be a better way to achieve this. 🤞

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member

Would you like to chime in?

You must be a member to start a new discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member