Back to Course |
Deploy Laravel Project to AWS EC2: Step-By-Step

Finally: Install Laravel Project

Finally! After all those preparations, we can see our project working in our browser.

Usually, NginX and PHP-FPM service runs as www-data user and it is a system user for services. In the best case scenario, we would like to isolate our web project from any system services and maybe have a different directory for example /home/web/demoproject as opposed to /var/www/html where you need root user explicitly.

  1. To add a new user enter the adduser web command as root. You will be prompted to define a password for a web user add fill in optional details. Make sure to choose a secure password.
root@ip-172-31-44-101:~# adduser web
Adding user `web' ...
Adding new group `web' (1001) ...
Adding new user `web' (1001) with group `web' ...
Creating home directory `/home/web' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for web
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
  1. Now we can log in with a web user and create a structure for how we want our future laravel project served.

We can easily do that by entering sudo su web or just su web if you're a root. The user you're currently logged in as can be seen in your command prompt or optionally can be checked with the whoami command.

ubuntu@ip-172-31-44-101:~$ sudo su web
web@ip-172-31-44-101:/home/ubuntu$ whoami
web

Navigate to your home directory, this can be done by entering cd without any parameters or cd ~ or cd /home/web. The present working directory can be checked with the pwd command.

web@ip-172-31-44-101:/home/ubuntu$ cd
web@ip-172-31-44-101:~$ pwd
/home/web

Now create a new directory for our demo project:

web@ip-172-31-44-101:~$ mkdir demoproject

And for this step, we just create a single PHP file to test future configurations.

web@ip-172-31-44-101:~$ cd demoproject/
web@ip-172-31-44-101:~/demoproject$ nano index.php

Here are the contents of our index.php file for now.

<?php
 
echo 'test';

Then press CTRL-X to save and exit the editor.

  1. Now we have a new user for our project and directory with some test files.

Currently, NginX and PHP-FPM would have no access to our /home/web directory, and we need a few more things to do for NginX to be able to serve and process this php file.

  1. Add the user to the www-data group. Current present groups our new users are in can be checked with the groups web command.
root@ip-172-31-44-101:~# groups web
web : web

To add our user to the www-data group. we can use the usermod command with -aG flags.

root@ip-172-31-44-101:~# usermod -aG www-data web

And then check groups again.

root@ip-172-31-44-101:~# groups web
web : web www-data

As we can see, the user has been added to the www-data group.

  1. Change /home/web folder permissions to allow services to read it using the chmod 755 /home/web command.
web@ip-172-31-44-101:~$ chmod 755 /home/web
  1. Edit NginX config
root@ip-172-31-44-101:/home/web# nano /etc/nginx/sites-enabled/default

Lines that we are interested in are near each other starting with root /var/www/html

It should be the 41st and 44th lines, they look like that:

root /var/www/html;
 
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;

Change root directive root /var/www/html to root /home/web/demoproject.

And as you have guessed the comment suggests we need to append index.php to the index directive:

Update index index.html index.htm index.nginx-debian.html; to index index.html index.htm index.nginx-debian.html index.php;.

The result should look like that:

root /home/web/demoproject;
 
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html index.php;

Full NginX default site configuration without comments looks like this:

server {
listen 80 default_server;
listen [::]:80 default_server;
 
root /home/web/demoproject;
 
index index.html index.htm index.nginx-debian.html index.php;
server_name _;
 
location / {
try_files $uri $uri/ =404;
}
 
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}

For sake of simplicity, we won't go deeper into NginX configuration nuances.

  1. Another config we need to update is the PHP-FPM pool. To be able to process php files we need to change the user and group php-fpm process pool is running on. It is possible to configure another pool, but now let's just update the default one. This can be done by editing /etc/php/8.1/fpm/pool.d.
root@ip-172-31-44-101:~# nano /etc/php/8.1/fpm/pool.d/www.conf

In the [www] section find lines user = www-data and group = www-data, they should not be too far from the beginning.

From

user = www-data
group = www-data

update it to

user = web
group = web

And exit by saving the file with CTRL-X.

  1. For changes to take effect it is necessary to restart NginX and PHP-FPM. Let's proceed with systemctl command from previous chapters.
root@ip-172-31-44-101:~# systemctl restart nginx
root@ip-172-31-44-101:~# systemctl restart php8.1-fpm

If you navigate to your site URL http://<YOUR-SERVER-IP-ADDRESS> you should see the site echoing test to confirm the configuration of services is successful.

  1. From this point there are several options for how to populate your Laravel project files into our new /home/web/demoproject directory. The simplest form would be to download your Laravel project archive on the server and extract it to the /home/web/demoproject directory or pull it from the GIT repository.

We will cover how to pull it from your GitHub repository by using the git clone command.

Since we will be cloning the existing repository we can delete the existing demoproject directory, because it will be created when we clone the Laravel project.

web@ip-172-31-44-101:~$ rm -fr demoproject/

To be able to clone the repository and later pull from it using the ssh method we need to generate a new ssh-key pair.

Note: even if the repository is public, you need to add your ssh key otherwise access would be denied.

Note: If you wish to clone the public repository you do not own, we suggest skipping a key generation and using the HTTPS method. For example, to copy the demo project we used in this tutorial, use git clone https://github.com/mc0de/rds-ec2-project.git demoproject and proceed to step 10.

To generate new ssh-key pair use the ssh-keygen command.

web@ip-172-31-44-101:~$ ssh-keygen -t ed25519 -C "your_email@example.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/web/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/web/.ssh/id_ed25519
Your public key has been saved in /home/web/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:/B0X+6/9X83V+PXvXGTIVYUTSPUjNSp8PMwBC+rvwvU your_email@example.com
The key's randomart image is:
+--[ED25519 256]--+
| . oo+o=+|
| . o * *.o|
| . + O.oo|
| .. o.o*o|
| .S . * *|
| .o . o *=|
| . ..o . O|
| o. E o=|
| .. .o@|
+----[SHA256]-----+

Your public key can be previewed using this command:

web@ip-172-31-44-101:~$ cat /home/web/.ssh/id_ed25519.pub

Now copy this key, go to your account settings on GitHub https://github.com/settings/keys and add it by pressing the [New SSH Key] button

GitHub New SSH Key

Fill in the form and submit it by pressing [Add SSH key].

Add new SSH key

Now go to your repository and copy the SSH URL:

GitHub repo SSH

And clone the repository:

web@ip-172-31-44-101:~$ git clone git@github.com:mc0de/rds-ec2-project.git demoproject
Cloning into 'demoproject'...
remote: Enumerating objects: 173, done.
remote: Counting objects: 100% (173/173), done.
remote: Compressing objects: 100% (130/130), done.
remote: Total 173 (delta 26), reused 173 (delta 26), pack-reused 0
Receiving objects: 100% (173/173), 119.98 KiB | 706.00 KiB/s, done.
Resolving deltas: 100% (26/26), done.

The last argument of the git clone git@github.com:mc0de/rds-ec2-project.git demoproject command is the directory where it should be stored otherwise it will use the GitHub repository name which is not always ideal.

  1. Up until this moment, a very simple NginX configuration was enough to test a single PHP file and our project isolation, but as you know Laravel is a lot more, and doesn't even have index.php in its root directory. So we need to update our NginX configuration once again. According to Official Documentation https://laravel.com/docs/9.x/deployment#nginx our configuration now should look like that:
root@ip-172-31-44-101:~# nano /etc/nginx/sites-enabled/default
server {
listen 80 default_server;
listen [::]:80 default_server;
 
root /home/web/demoproject/public;
 
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
 
index index.php;
 
charset utf-8;
 
server_name _;
 
location / {
try_files $uri $uri/ /index.php?$query_string;
}
 
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
 
error_page 404 /index.php;
 
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
 
location ~ /\.(?!well-known).* {
deny all;
}
}

As we see our document root is now /home/web/demoproject/public, index directive has only an index.php value because this is the only entry point the Laravel application has and a bunch of other settings. More information on various configurations can be found on Official NginX Documentation https://www.nginx.com/resources/wiki/start/#pre-canned-configurations.

  1. From this point road will be a lot less bumpy. The hardest part in the past. It is time to set up the Laravel project using the usual steps you do in your development environment.

Navigate to your project directory:

web@ip-172-31-44-101:~$ cd demoproject/

Copy .env.example to .env

web@ip-172-31-44-101:~/demoproject$ cp .env.example .env

Fill in your database credentials in the .env file when we were settings the RDS instance in the previous chapter.

web@ip-172-31-44-101:~/demoproject$ nano .env
DB_HOST=database-1.cbd1u2cfua0q.eu-central-1.rds.amazonaws.com
DB_DATABASE=demo_project
DB_USERNAME=demo_user
DB_PASSWORD=******

Run composer install

web@ip-172-31-44-101:~/demoproject$ composer install
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Package operations: 108 installs, 0 updates, 0 removals
As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.
This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.
Installing 'unzip' or '7z' may remediate them.
- Downloading doctrine/inflector (2.0.6)
- Downloading doctrine/lexer (1.2.3)
<...>
INFO Discovering packages.
 
laravel/breeze .............................................................................................................................. DONE
laravel/sail ................................................................................................................................ DONE
laravel/sanctum ............................................................................................................................. DONE
laravel/tinker .............................................................................................................................. DONE
nesbot/carbon ............................................................................................................................... DONE
nunomaduro/collision ........................................................................................................................ DONE
nunomaduro/termwind ......................................................................................................................... DONE
spatie/laravel-ignition ..................................................................................................................... DONE
 
81 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

Run php artisan key:generate

web@ip-172-31-44-101:~/demoproject$ php artisan key:generate
 
INFO Application key set successfully.

Run php artisan storage:link to make necessary symlinks for accessing files in public storage

web@ip-172-31-44-101:~/demoproject$ php artisan storage:link
 
INFO The [public/storage] link has been connected to [storage/app/public].

Run migrations using php artisan migrate

web@ip-172-31-44-101:~/demoproject$ php artisan migrate
 
INFO Preparing database.
 
Creating migration table ............................................................................................................... 58ms DONE
 
INFO Running migrations.
 
2014_10_12_000000_create_users_table ................................................................................................... 78ms DONE
2014_10_12_100000_create_password_resets_table ......................................................................................... 34ms DONE
2019_08_19_000000_create_failed_jobs_table ............................................................................................. 40ms DONE
2019_12_14_000001_create_personal_access_tokens_table .................................................................................. 62ms DONE
  1. To later update your repository on the server after you pushed some changes:

Login to server:

$ ssh -i ~/.ssh/ec2-demo-web-ubuntu-server.pem ubuntu@ubuntu-aws

Login as a web user:

ubuntu@ip-172-31-44-101:~$ sudo su web

Navigate to your project's directory:

web@ip-172-31-44-101:/home/ubuntu$ cd /home/web/demoproject/

Issue git pull command:

web@ip-172-31-44-101:~/demoproject$ git pull
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 3), reused 9 (delta 3), pack-reused 0
Unpacking objects: 100% (9/9), 54.81 KiB | 597.00 KiB/s, done.
From github.com:mc0de/rds-ec2-project
901ecc4..50ae9b6 main -> origin/main
Updating 901ecc4..50ae9b6
Fast-forward
.gitignore | 1 -
public/build/assets/app.73cd3409.css | 1 +
public/build/assets/app.d426e523.js | 32 ++++++++++++++++++++++++++++++++
public/build/manifest.json | 12 ++++++++++++
4 files changed, 45 insertions(+), 1 deletion(-)
create mode 100644 public/build/assets/app.73cd3409.css
create mode 100644 public/build/assets/app.d426e523.js
create mode 100644 public/build/manifest.json
  1. Now your project is LIVE, let's share it with some friends! But wait, the URL http://<YOUR-SERVER-IP-ADDRESS> is very hard to memorize and not convenient at all. You may purchase a domain name on one of the providers and add DNS A type record with the value of your public IP address.

On your domain provider's panel entry should be similar to this:

A aws-course.laraveldaily.com 18.195.117.231 # your actual server ip address

or if you don't use a subdomain it even may look similar to that, using @ instead of name:

A @ 18.195.117.231

Configuration used in this tutorial doesn't need any additional changes on the server to support the domain name, all HTTP requests will resolve to the default site.


Congratulations, you've completed this "Deploy Laravel to AWS" course!

Our demo project used in this tutorial with all exact configurations can be accessed at aws-course.laraveldaily.com