How to install Laravel on Digital Ocean Cloud Instance

This blog post is a helper document for novice Laravel developers trying to set up a Laravel app on a Digital Ocean Cloud Instance. We have considered a basic shared CPU with CentOS operating system. 

The systems requirements for Laravel are rather modest:

  • PHP >= 7.3
  • BCMath PHP Extension
  • Ctype PHP Extension
  • Fileinfo PHP Extension
  • JSON PHP Extension
  • Mbstring PHP Extension
  • OpenSSL PHP Extension
  • PDO PHP Extension
  • Tokenizer PHP Extension
  • XML PHP Extension

The example commands are prefixed with sudo, but you can omit that part if you are running as root.

Step 1: Add PHP 7.3 Remi repository

Since we need PHP 7, let us add access to both EPEL and the Webtatic repository to gain access to the packages available there. 
sudo rpm -Uvh 
sudo rpm -Uvh 

Once those commands have been completed successfully, please run: 
sudo yum check-update 

and then 

sudo yum search php73 

should return a good-sized list of php73w- packages including: 
mod_php73w.x86_64 : PHP module for the Apache HTTP Server 
php73w-fpm.x86_64 : PHP FastCGI Process Manager 

Step 2: Install PHP 7.3 on CentOS 8

sudo yum install nginx1w php73w-fpm php73w-pdo php73w-mbstring php73w-xml php73w-common php73w-cli 

Check version installed 

$ php -v/code>

Step 3: Installing other PHP 7.3 Extensions

Install PHP 7.3 extensions by using the syntax: 
sudo yum install php73w-< entension-name > 
As an example, to install a mysql module for PHP applications that use MySQL databases, you’ll run: 
sudo yum install php73w-mysql 
Configure Nginx and PHP-FPM 
To get nginx running we need to open up access to port 80 through the firewall: 
sudo firewall-cmd --add-port=80/tcp 
sudo firewall-cmd --permanent --add-port=80/tcp 

Now start nginx with the default config by running: 
sudo systemctl start nginx 

If you want nginx to be started when the server boots, then also run: 

sudo systemctl enable nginx 

Now if you visit the IP address of your server you should see the default nginx page. 
With basic static content being served properly, lets start php-fpm. 

sudo systemctl start php-fpm 

Once again, if you want php-fpm to be started when the server boots, then also run: 

sudo systemctl enable php-fpm 

By default, that will start listening for connections on localhost port 9000. We can follow the nginx PHP FastCGI example to allow nginx to pass requests for PHP pages to php-fpm. 
For now we'll place the contents of the location directive shown on that page into the /etc/nginx/nginx.conf file near the bottom of the default server block that starts on line 49. (You can use whatever editor you like. For vi the full command would be sudo vi /etc/nginx/nginx.conf) 
location ~ [^/]\.php(/|$) { fastcgi_split_path_info ^(.+?\.php)(/.*)$; if (!-f $document_root$fastcgi_script_name) { return 404;} 
# Mitigate vulnerabilities 
fastcgi_param HTTP_PROXY ""; 
fastcgi_index index.php; 
# include the fastcgi_param setting 
include fastcgi_params; 
# SCRIPT_FILENAME parameter is used for PHP FPM determining 
# the script name. If it is not set in fastcgi_params file, 
# i.e. /etc/nginx/fastcgi_params or in the parent contexts, 
# please comment off following line: 
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;} 

Note: Make sure and uncomment the last commented line of what we are pasting in. 

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 

If we do not have that uncommented, then we will not see any PHP output in our browser when we run a test in a minute. Now restart nginx and php-fpm with the following commands: 

sudo systemctl restart nginx 
sudo systemctl restart php-fpm

Install Composer and Laravel

We should now be ready to install composer and use it to install Laravel. I'm running these commands from inside my home directory. ( cd ~ ) Run this to automatically download a script that will be used to install composer. 

php -r "copy('', 'composer-setup.php');" 

The suggested method to verify the installation script hasn't been tampered with. 
php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" 

That should respond with "Installer verified". If it does, then run: 
php composer-setup.php 

Which will respond with: 
All settings correct for using Composer 
Composer (version 1.5.2) successfully installed to: /usr/share/nginx/html/composer.phar 
Use it: php composer.phar 
Remove the install script with: 
rm composer-setup.php 
Which may prompt you for confirmation: 
rm: remove regular file composer-setup.php? y 
Now we can run: 
php composer.phar 
For convenience, you can copy composer.phar into a directory that is included in the $PATH environment variable. For example: 

sudo cp composer.phar /usr/bin/composer 

Or you could create a symbolic link to it using: sudo ln -s /home/YOUR_USERNAME/composer /usr/bin/composer.phar 

then you can execute it by simply running composer. 
Lets go ahead and move to the nginx document root in /usr/share/nginx/html: 
cd /usr/share/nginx/html 
To install the Laravel framework into a new example-app directory using composer, run: 

sudo composer create-project laravel/laravel example-app 

Once that has completed, we need to adjust our nginx document root and then test. 
Edit /etc/nginx/nginx.conf and around line 52 in the default server block you should see the line: 
root /usr/share/nginx/html; 

We need to change it to: 
root /usr/share/nginx/html/example-app/public; 
Now we can check our syntax and then restart nginx: 
sudo nginx -t 
sudo systemctl restart nginx Resolve Ownership and SELinux Errors 
SELinux is enabled by default when using the ProfitBricks CentOS 7 image. You can determine if this is the case with your server by running: 
sudo sestatus 
It will return some results similar this: 

SELinux status: enabled 
SELinuxfs mount: /sys/fs/selinux 
SELinux root directory: /etc/selinux 
Loaded policy name: targeted 
Current mode: enforcing 
Mode from config file: enforcing 
Policy MLS status: enabled 
Policy deny_unknown status: allowed 
Memory protection checking: actual (secure) 
Max kernel policy version: 33 
If it shows SELinux status: enabled and Current mode: enforcing then this section will likely apply to you and you'll need to follow the next set of steps. If SELinux has been disabled or the mode is permissive then you can skip this. Please try to access http://YOUR_SERVER_IP_ADDRESS/index.php in a browser now. If SELinux is enabled you will likely see an UnexpectedValueException error screen. 

This error can be caused by the directory and file ownership along with the security context being enforced by SELinux on a couple of directories that Laravel needs to write to. We can work around this by running: 

sudo chown -R apache:root /usr/share/nginx/html/example-app/storage/* 
sudo chown -R apache:root /usr/share/nginx/html/example-app/bootstrap/cache 

This changes the ownership of those directories to apache which is the user that php-fpm is running as by default. 
To resolve the SELinux issue, we need to update the security context from httpd_sys_content_t to httpd_sys_rw_content_t. This can be done by running semanage which is part of the policycoreutils-python package. (You can install it with sudo yum install policycoreutils-python if necessary, but it should already be installed when using the ProfitBricks CentOS 7 image.) 
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/usr/share/nginx/html/example-app/bootstrap/cache(/.*)?' 
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/usr/share/nginx/html/example-app/storage(/.*)?' 

Then to apply those changes run: 
sudo restorecon -Rv '/usr/share/nginx/html/example-app' 

and if you want to verify the change, run ls -Z which should generate output similar to this: 

drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 app 
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 artisan 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 bootstrap 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 composer.json 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 composer.lock 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 config 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 database 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 package.json 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 phpunit.xml 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 public 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 resources 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 routes 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 server.php 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_rw_content_t:s0 storage 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 tests 
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 vendor 
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 webpack.mix.js 
You'll notice that storage shows httpd_sys_rw_content_t instead of httpd_sys_content_t. The same should be true of the directories under bootstrap