StepByStep Guide To Deploying Laravel Applications On Virtual Private Servers

From Doku Wiki
Jump to: navigation, search

Laravel makes it easy to create modern web applications. But, deploying them onto a real server is another matter.



There are many options.



PaaS like Heroku or AWS Elastic Beanstalk, unmanaged virtual private servers, shared hosting, and so on.



Using cPanel, you can easily deploy a Laravel App on a shared host. Simply zip up the source code with all dependencies, and upload it to the server. But on shared hosting, you don't have much control over the server.



PaaS such as Heroku or AWS Elastic Beanstalk strike a good balance between ease-of-use and control. However, they can be costly at times. A standard 1x dyno from Heroku, for example, costs $25 per month and comes with only 512MB of RAM.



Unmanaged virtual private servers are affordable and provide a lot of control on the server. A server with 2GB RAM, 20GB SSD space and 2TB transfer bandwidth is available for $15 per month.



Now the problem with unmanaged virtual private servers is that they are unmanaged. You'll be responsible for installing all necessary software, configuring them, and keeping them updated.



In this article, I'll guide you step-by-step in the process of how to deploy a Laravel project on an unmanaged virtual private server (we'll refer to it as VPS from now on). To see the benefits of the framework, first read this article. Let's get started if you are ready.



Prerequisites



This article assumes previous experience with Linux command lines. Ubuntu will be used by the server's operating system. You will need to complete all tasks using the terminal. The article also expects you to understand basic concepts like Sudo, file permissions, differences between a root and non-root user, and git.



Project Code and Deployment Plan



I've built a dummy project for this article. It's a simple discussion board application that allows users post questions and others to answer them. You can consider this a dumbed-down version of StackOverflow.



The project source code is available on https://github.com/fhsinchy/guide-to-deploying-laravel-on-vps repository. Make a fork of this repository and clone it on your local computer.



Once you have a copy the project on your computer you are ready to begin the Laravel deployment process. You'll need to create a VPS and configure a way to push the source from your computer to the server.



Provisioning a new Ubuntu Server



There are many VPS service providers available, including DigitalOcean and Vultr, Linode and Hetzner. While the process of working on an unmanaged virtual private server is the same for all providers, they do not offer the same services.



DigitalOcean, for example, provides managed database services. Vultr (on the other hand) doesn't have such services. These differences are not important.



I'll only show you the most unmanaged method of doing things. So regardless of the provider, you're using, the steps should be identical.



Before provisioning a new server, you'll have to generate SSH keys.



New SSH keys created



Wikipedia states that Secure Shell (SSH), a cryptographic network protocol, allows network services to be operated securely over an unsecure network. It allows you connect to remote servers using a key-pair or password.



If you're already familiar with SSH and have previously generated SSH key-pairs on your computer, you may skip this subsection. The following command will generate a new key-pair for macOS, Linux, and Windows 10 machines:



There will be several prompts displayed on the terminal. You can navigate through them by pressing enter. You don't have to put any password either. Once you have generated your key-pair, you will find a file called id_rsa.pub within the /.ssh/ directories. You'll need this file when provisioning a new VPS.



Provisioning a New Virtual Private Server



I've already said there are some differences between the VPS service providers, so if you want to be absolutely in line with this article, use DigitalOcean.



A single virtual private server on DigitalOcean is known as a droplet. On Vultr it's called an instance and on Linode it's a linode. Log in to your provider and create a new virtual private server. Use Ubuntu 20.04 LTS as the operating system.



You can choose the one with 1GB RAM or 25GB SSD storage. It should cost you around $5 per month. Choose the closest region to your users. I am a Bangladeshi citizen, and my users are mostly from Bangladesh. Therefore, I deploy my applications in Singapore.



Create a new SSH key under the SSH Section. Copy the content from the ~/.ssh/id_rsa.pub file and paste it as the content. Save the key by giving it a descriptive name.



You can also leave the rest of these options unaffected. Most providers offer an automatic backup service. For this demonstration, keep that option disabled. But in a real scenario, it can be a lifesaver. Once the process is completed, you can connect to your new server via SSH.



Performing Basic Setup



Now that your server has been set up, it's time for some basic setup. First, use SSH with the server IP address to log in as the root user.



The server's IP address can be found on the dashboard or in the server details. Once you have logged into the server, the first step is to create a new nonroot user.



By default, all servers come with the root user. As you may already know the root user is powerful. Hackers can cause havoc if they hack into your server and log in as root user. This can be avoided by disabling root login.



Logging in with a key-pair is safer than using a password. All users should disable the ability to log in using a password.



To create a new user from the terminal, execute the following command inside your server:



You can name your nonroot user anything you like. Nonroot was chosen as the name to emphasize that this is a nonroot user. The adduser will ask for a password, and other information. You can use a strong password, and leave the rest blank.



After creating the user, you will need to add this user to the sudo user group. Otherwise, the nonroot user will be unable to execute commands using sudo.



In this command, sudo is the group name, and nonroot is the username. You'll get an error when you try to log in to the account.



This is because most VPS providers do not allow you to login with a password. One easy way to fix this is to copy the content of /root/.ssh directory to the /home/nonroot/.ssh directory. You can use the rsync program to do this.



The --archive option allows you to rsync copy directories recursively, preserving symbolic links, user ownership and group ownership as well as timestamps. The --chown option sets the nonroot user as the owner in the destination. Now you should have the ability to log in using SSH as the new account.



After logging in as non-root, you will need to update the operating system. This includes all installed programs. The following command is required to do so:



Downloading and installing the updates will take a few minutes. If you see a screen called "Configuring openssh Server" asking about file changes, then select the "Keep the local version currently in use" option and hit enter.



After the update process finishes, reboot the server by executing the sudo reboot command. Wait for the server's reboot to complete and log back in as non-root.



Deploying Code on the Client



Next, you will need to deploy the code on the server after you have completed the basic setups. I've seen people clone a repository somewhere on their production server, and log into the server to perform a pull whenever they make any changes to the code.



There is an easier way. Instead of logging into the server to perform a pull, you can use the server itself as a repository and push code directly to the server. You can also automate the post-deployment steps like installing dependencies, running the migrations, and so on, which will make the Laravel deploy to server an effortless action. Before you can do all this, you will need to install PHP and Composer.



Installing PHP



You can find a list of PHP packages required by Laravel on the official docs. To install all these packages, run the following command on your server.



Depending on if you're using MySQL, PostgreSQL and SQLite for your project, one of the following packages will be required:



The following package includes support for Redis' in-memory databases.



Apart from these packages, you'll also need php-curl, php-zip, zip, unzip, and curl utilities.



The question bank project uses MySQL as its database system and Redis for caching and running queues, so you'll have to install the php7.4-mysql and the php7.4-redis packages.



Depending on the project, you may have to install more PHP packages. For example, projects that use images for work depend on the php–gd package. You don't need to mention the PHP version in every package name. APT will automatically install the most recent version if you don’t specify a number.



This article was written using PHP 7.4 as the most recent version of Ubuntu's package repositories. However, the question board project requires PHP 7.

PHP 4 and PHP 8 could become the default, I've indicated the version number in this article.

Installing Composer



After installing PHP, and all the required packages, you're now ready to install Composer. Navigate to the official composer download page. Follow the command-line instructions or execute these commands.



Now that PHP has been installed on your server, you can set up automated deployment of your code.



Git allows you to deploy code



To automate code deployment on a server, log in with a non-root user. This directory will be used as the repository. You can also push production code to it.



The -p option to the mkdir command will create any nonexistent parent repository. Next, cd into that directory and create a new empty git repository.



A bare is a git repository that doesn't contain a working tree. The practical usage of such a git repository is as a remote origin. Don't worry if I didn't explain everything. As you continue to go, things will become clearer.



Assuming you're still inside the /home/nonroot/repo/question-board.git directory, cd inside the hooks subdirectory and create a new file called post-receive.



Files inside this directory are regular shell scripts that git invokes when some major event happens on a repository. When you push code code, git will wait for all the code to be received before calling a post-receive Script.



If you are still within the hooks directory then open the postreceive script and execute the following command.



As you may already know, you will need to create /sbin/deploy. The /sbin directory houses scripts that perform administrative tasks. Touch the /sbin/deploy command and use the nano text editor to open it.



Now update the script's content as follows:



This shell script is evident by the #!/bin/sh-line. After that line, the only line of code in this script copies the content of the /home/nonroot/repo/question-board.git repository to the /srv/question-board directory.



The --work_tree option indicates the destination directory, while --git-dir specifies the source repository. I like to use the /srv directory for storing files served by this server. You can also use the directory /var/www.



Save the file using Ctrl+O and exit nano by pressing Ctrl+X key combination. You can verify that the script has executable permissions by running the following command.



To make this process work, the last step is to create the destination directory or the work tree. To do this, run the following command.



Now you have a working tree directory, a repository bare, and a hook that calls the sudo /sbin/deployscript. But, how would the post-receive hook invoke the /sbin/deploy script using sudo without a password?



Open the /etc/sudoers.dll file on your server with the nano editor. Add the following line code at the file's end:



This line of code means that the nonroot user will be able to execute the /sbin/deploy script with sudo on ALL hosts with NOPASSWD or no password. Save the file by pressing Ctrl+ O. To exit nano, press the Ctrl+ K key combination.



Now you are ready to push the source code of your project. Assuming that you've already forked and cloned the https://github.com/fhsinchy/guide-to-deploying-laravel-on-vps repository on your local system, open up your terminal on the project root and execute the following command:



Replace my IP address with that of your server. If the stable code is not the master branch, you can push the code to the server using the following command:



After you have sent the code to the server log back in as non-root and cd into /srv/questionboard directory. You will see that git has successfully verified your project code by using the ls command.



Automating Post Deployment Steps



Congratulations for being able directly to deploy Laravel on the server. Is that enough? What about the post-deployment tasks? Tasks such as installing or updating dependencies and migrating the databases, caching views, configurations and routes, restarting employees, and so forth.



Automating these tasks can be much simpler than you think. You just need to create a script to automate these tasks, give permissions, then call the script from within the post-receive hook.



Another script, called post-deploy, should be created in the /sbin folder. After creating the file you can open it in the nano text editor.



Follow these steps to update the content in the post-deployment text. Don't worry if you don't clearly understand everything. I will explain each line in detail.



The first line changes to the /srv/question board directory. The second line copies the.env.example.file. The -n option prevents the cp commands from overriding files that are already in use.



The third and fourth commands will install all the necessary dependencies and update them if necessary. The COMPOSER_ALLOW_SUPERUSER environment variable disables a warning about running the composer binary as root.



Save the file using Ctrl+O and exit nano with Ctrl+X key combination. Make sure that the script has executable permission by executing the following command:



Open the /home/nonroot/repo/question-board.git/hooks/post-receive script with nano and append the following line after the sudo /sbin/deploy script call:



After calling the deployscript, ensure that you call post-deploy. Save the file using Ctrl+O and exit nano with the Ctrl+K key combination.



Open the /etc/sudoers directory on your server once more using the nano editor. Edit the line you just added as follows:



Save the file by pressing Ctrl + O and exit nano by pressing the Ctrl + K key combination. If necessary, you can add additional post deploy steps to the script.



Make some changes to your code to test the new post-deployment script. Then, commit the changes to the production master branch. You'll now see the progress of composer packages installation on the terminal, as well as outputs from other artisans calls.



Once the deployment process finishes, log back into the server, cd into the /srv/question-board directory, and list the content by executing the following command:



You'll also see an env file and a vendor directory. You can now generate the Laravel application encryption key. To do this, follow the following command:



You'll notice the APP_KEY field is filled with a long string if you examine the contents of the.env files using the nanotext editor.



Configuring NGINX and Installation



Once you have successfully pushed your source code to the server the next step is installing a web server to host your application. I'll use NGINX in the article. You can use Apache or another program.



This article will focus only on configuring the webserver to host a Laravel program. It will not go into detail about NGINX-related stuff. NGINX is a complex program. The NGINX Handbook can be a great resource to help you learn the basics of NGINX.



The following command will install NGINX on Ubuntu Server:



This command should install NGINX, and register as a systemd services. To verify, you can execute the following command:



You should see something as follows in the output:



You can regain control over the terminal by pressing q on your keyboard. If you visit the server IP address, you will see NGINX's default welcome page.



You'll have to change the NGINX configuration to serve your Laravel application instead. To do so, create a new file /etc/nginx/sites-available/question-board and open the file using the nano text editor.



This file will contain NGINX configuration code to serve the question board application. Configuring NGINX from scratch can be difficult, but the official Laravel docs have a pretty good configuration. Follows is the code copied from the docs:



This code is unchanged, except for the first two lines. Make sure to use the IP address from your server for the server_name, and that root points to correct directory. In a later section, replace this IP address for a domain.



Also, inside the location ~ \.php$ block, make sure that the fastcgi_pass directive is pointing to the correct PHP version. This demonstration is using PHP 7.4. If you are using PHP 8.0 or 8.1, update the code accordingly.



If you cd in the directory /etc/nginx and list the content using ls, you'll notice two folders named sites available and sites-enabled.



The sites-available folder holds all the different configuration files serving applications (yes, there can be multiple) from this server.



The sites-enabled folder, on the other hand, contains symbolic links to the active configuration files. So if you do not make a symbolic link of the /etc/nginx/sites-available/question-board file inside the sites-enabled folder, it'll not work. To do this, use the following command.



To avoid any unintended conflicts, the second command deletes the default configuration file.

This command will check if the code is correct.

If everything is fine, you can reload NGINX configuration by running the following command.



You can see your server IP address and you will see that NGINX is properly serving your application but the application is throwing 500 internal server errors.



As you can clearly see, the application attempts to write the logs folder but fails. This happens because the root user has the /srv/question board directory and the www-data user has the NGINX process. To make the /srv/question-board/storage directory writable by the application, you'll have to alter the directory permissions.



Configuring directory permissions



There are many options for configuring directory permissions in Laravel projects. But, I'll show what I use. First, you need to make sure that the www-data user who owns NGINX is also the owner for the /srv/question board directory. To do so, execute the following command:



Then, set the permission of the /srv/question-board/storage to 755, which means read and execute access for all users and write access for the owner by executing the following command:



Last, you need to make another subdirectory writable. That is the /srv/question-board/bootstrap/cache directory. To do so, follow the following command.



You should be able to see the application working fine if you go back to the IP address of the server and refresh.



MySQL installation and configuration



Now that you have successfully configured and installed NGINX, it's now time to install MySQL. To do this, execute the following command to install MySQL.



To make MySQL installation more secure, you can execute the following command after the installation process is completed:



First, the script will ask if you want to use the validate password component or not. Input "Y" as the answer and hit enter. Next, you will have to select the level of difficulty for your password. I recommend setting it high. Even though it can be annoying to choose a difficult password for each user you create, it is best to do so for security reasons. Next, set a secure passphrase for the root user. You can put "Y" as the answer for the rest of the questions. Give the questions a read if you want to.



Before you can log in to your database server as root you will need to switch to root. To do so, execute the following command:



Log into your database server as root by executing the following command:



Once you are in, create a brand new database for the questionboard application by running the following SQL code.



Next, create a new database user by executing the following SQL code:



To clarify that this is a nonroot user, I used the nonroot name. You can use whatever name you wish. You can also replace the word password by something more secure.



After that, give the user full access to the question_board data by executing this SQL code:



In this code, question_board. * refers all tables in the question_board table database. Finally, quit MySQL client by running the q command. The exit command will then be invoked to exit the root shell.



Try logging in as a nonroot user now by running the following command



The MySQL client will require the password. Use the password that you created when creating the nonroot account. If you log in successfully, exit MySQL Client by executing the "q" command.



Now that your database server is up and running, it's time configure the question-board project to make it work. First, navigate to /srv/questionboard directory. Next, use the nano text editor and open the file env.



Update the database configuration as follows:



Make sure to replace the username and password with yours. Save the file by pressing Ctrl + O and exit nano by pressing Ctrl + X key combination. To test out the database connection, try migrating the database by executing the following command:



If everything goes fine, that means the database connection is working. The project comes with two Seeder Classes, one to seed the admin user and the other for the categories. To run them, follow the following commands:



Now, if you visit the server IP address and navigate to the /questions route, you'll see the list of categories. You will also need to log in as admin using the following credentials



If you've worked with Laravel long enough, you may have already realized that it is common to add new migrations files whenever there is an update to the database. To automate the process of running every deployment's migrations, open the "/sbin/post_deploy" script with nano and add the following line to the end:



The --force option will stop an artisan warning about running production environment migrations. Seeders should be run only once, not like migrations. If you add new seeders on later deployments, you'll have to run them manually.



Configure Laravel Horizon



Laravel Horizon has been pre-installed on the question board project. Once Redis is running, you can begin processing jobs.



The official docs suggest using the supervisor program for running Laravel Horizon on a production server. The following command will install the program:



Supervisor configuration files live within your server's /etc/supervisor/conf.d directory. Create a new file /etc/supervisor/conf.d/horizon.conf and open it using the nano text editor:



Follow these steps to update the file's contents:



Save the file by pressing Ctrl+ O. To exit nano, press the Ctrl+ X key combination. Execute the following commands to update your supervisor configuration and start the Horizon process:



To test out if Laravel Horizon is running or not, visit your server's IP address and navigate to the /login page. Log in as an admin user to navigate to the route /horizon. Laravel Horizon is in active mode.



Laravel Horizon has been configured to only allow the admin user in. This means that if you log into Laravel Horizon with any other credential, you'll get a 403 forbidden error message when you go to the /horizon route.



One thing that can catch many people off guard is the fact that if you make changes in your jobs, Laravel Horizon will need restarted to take those changes into account. I recommend adding a line into the /sbin/post_deployscript to restart Laravel Horizon for every deployment.



To do so, open the /sbin/post-deploy using the nano text editor and append the following line at the end of the file:



This command will stop the Laravel Horizon process and restart it on each deployment.



Configuring a Domain Name with HTTPS



This step will require you to have your own domain name. Server list I'll use the questionboard.farhan.dev domain name for this demonstration.



Log in to the domain name provider you prefer and go to DNS settings. A DNS record of type Type A is required to point a domain name at an IP address.



To do this, create a new DNS Record with the following attributes:



Type: A Record



Host: questionboard



Value: 104.248.157.172



You must replace my IP address by yours. If you want your top domain to point at an IP address, instead of a Subdomain, simply add a @ as the host.



Now go back to your server and open the /etc/nginx/sites-available/questionboard config file using the nano text editor. You can now change your domain name by removing the IP address from server_name directive. Do not put HTTP or HTTPS at the beginning.



Multiple domain names can be entered, such as the top-level domain or the www subdomain. These domain names can be separated by spaces. Save the configuration file by pressing Ctrl+ O or Ctrl+ X key combination. This command will allow you to restart NGINX configuration.



Now you can access your application with your domain name and not the IP address. You can use the certbot software to enable HTTPS for your application.



To do so, execute the following command to install certbot:



It is a python script that allows you to easily use SSL certificates for free. To obtain a new SSL certificate, you can execute the following command:



First, the program asks for your email address. Next, it will ask you if the terms and conditions are acceptable to you.



Then, it will ask you to share an email address with Electronic Frontier Foundation.



The third step will allow the program to read the NGINX configuration files and extract domain names from the service_name directive. Look at the domain names it shows and press enter if they are all correct. After deploying the new certificate, the program will congratulate you, and now you've got free HTTPS protection for 90 days.



After 90 days, it will attempt to renew your certificate automatically. To test the auto-renew feature, execute the following command:



If the simulation goes well, you are good to go.



Configuring a Firewall



For server security, a properly configured firewall can be very helpful. In this article, I'll show you how you can configure the popular UFW program.



UFW is an uncomplicated firewall that comes as a default in Ubuntu. UFW is configured to allow all outgoing traffic to the server by default. To do this, use the following command.



All incoming traffic is blocked. This means that you and no one else will be able access your server.

The next step is to allow incoming requests in three specific ports. These ports are:

Port 80, used for HTTP traffic.



Port 443, used for HTTPS traffic



Port 22 is used to handle SSH traffic.



Follow these commands to do this:



Finally, you can enable UFW by issuing the following command:



That's it. Your server now allows HTTP, HTTPS and SSH traffic to come from the outside. This makes it a bit more secure.



Laravel Optimizations after deployment



Your application is almost ready for international requests. One last step that I would like to suggest is caching the Laravel configuration, views, and routes for better performance.



To do this, use the nano text editor to open the /sbin/postdeploy script and add the following lines to the end of the file.



The caches will now be automatically cleared and renewed for every deployment. Also, make sure you set the production and false APP_DEBUG options in your env files. Otherwise, you may unintentionally compromise sensitive information regarding your server.



I would like to express my gratitude to all Laravel developers that took the time and effort to read this article. I hope you enjoyed it, and that you have gained some useful knowledge about application deployment. If you want to learn more about NGINX, consider checking out my open-source NGINX Handbook with tons of fun content and examples.



You can also read the Laravel Symfony, Laravel Corcel and Laravel Blockchain articles if you wish to deepen your knowledge of Laravel.



If you have any questions or confusion, feel free to reach out to me. I am available on Twitter, LinkedIn and always happy and able to help. Keep safe and keep learning.