Deploying Laravel Projects with Git and Capistrano to Nginx Server

PHP
In this article, we will get our newly created server to ready for deployment. First, we will create a new user for deployment. Then we will install the software that is needed to run our Laravel project. Moreover, we will set the Nginx and php-fpm configuration. Hence, we will connect the server and the git repository. Finally we will set the Capistrano config and publish our project to the server.

Prerequisites of this article are, newly created Ubuntu server(actually any other Linux servers are fine too, just change Debian commands) and Github or Bitbucket repository where you host your project files.

Part I - Creating an User for Deployment

You should never and ever use your root account for deployment. That's why you should set up a new account for deployment. First, if you haven't generated a SSH key before, create it in your local machine, not in the server. If you are not sure about it, check it first.
$ ls -al ~/.ssh
If this returns you an output, then skip the part below and jump to the first paragraph starting with Lastly. If not, create your ssh key in your local machine.
$ ssh-keygen -t rsa -b 4096 -C "your@email.com"
This will prompt you for some answers, skip the first one with hitting enter, for the second question, it will prompt for a passphrase, write something complex. Then it will ask it one more time for verification, write it again and then it will generate your SSH key. The output of this process will be two files, one is id_rsa which is your private key and the other one is id_rsa.pub which is your public key that we will use for communicating with our server. Finally add your passphrase to ssh-agent for avoiding passphrase asking everytime you try to connect to your server with SSH.
$ eval `ssh-agent -s`
$ ssh-add ~/.ssh/id_rsa
Lastly, copy the contents of id_rsa.pub file, either just opening it with a text editor or with xclip like below.
$ sudo apt-get install xclip
$ xclip -sel clip < ~/.ssh/id_rsa.pub
Connect to your newly created server with the root account. Then create the new user deployer that belongs to the www-data group.
$ apt-get update
$ apt-get upgrade
$ useradd deployer
$ usermod -a -G www-data deployer
$ mkdir -p /home/deployer/.ssh
$ chmod 700 /home/deployer/.ssh
$ nano /home/deployer/.ssh/authorized_keys
Within the nano screen, paste the contents of your public key then hit CTRL + X and Y then Enter. After that, continue with the commands below.
$ chmod 400 /home/deployer/.ssh/authorized_keys
$ chown deployer:deployer /home/deployer -R
Thus, reconfigure the sudo privileges.
$ visudo
Within the nano screen you will see after hitting visudo, configure it as below and either comment or delete the other lines, then again hit CTRL + X and Y then Enter.
root     ALL=(ALL) ALL
deployer ALL=(ALL) NOPASSWD: /usr/sbin/service php7.1-fpm restart
The line starting with deploy may be confusing for you, what we do is, we let deployer user to run /usr/sbin/service php7.1-fpm restart command with sudo privileges without providing a password. The reason for this is to avoid no input file specified errors after a new deployment. Finally, forbid anyone to access the server with a password or as a root.
$ nano /etc/ssh/sshd_config
Edit the file as below.
PermitRootLogin no
PasswordAuthentication no
Again hit CTRL + X and Y then Enter. And then restart the ssh service.
$ service ssh restart
Now we are done with the initialization of our server.

Part II - Installing Software

We will run our project with Nginx server with php7.1-fpm. Thus we will use git to fetch our project files and composer to install vendor files.

Some of the packages are optional, configure it for yourself.
$ add-apt-repository ppa:ondrej/php
$ apt-get update
$ apt-get upgrade
$ apt-get install acl nginx mcrypt keychain memcached anacron redis-server php7.1-fpm php7.1-cli php7.1-mcrypt php7.1-cgi php7.1-zip php7.1-mbstring php7.1-curl php7.1-gd php7.1-intl php7.1-mysql php7.1-bz2 php-memcached php-xml git mysql-server mysql-client
$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

Part III - Configuring the Nginx and php-fpm

First we need to create the folders where we will deploy and set the required permissions.
$ mkdir -p /var/www/domain.com
$ mkdir -p /var/log/nginx/domain.com
$ chown -R root:www-data /var/www
$ chmod -R 0775 /var/www
$ find /var/www -type d -exec chmod 2775 {} +;
$ find /var/www -type f -exec chmod 0664 {} +;
Then we will inform Nginx that we will use the folders that we have created to run our application. To do that, let's create an configuration file for our domain using the default one.
$ cp /etc/nginx/sites-available/default /etc/nginx/sites-available/domain.com
$ ln -s /etc/nginx/sites-available/domain.com /etc/nginx/sites-enabled/domain.com
$ rm /etc/nginx/sites-enabled/default
$ nano /etc/nginx/sites-available/domain.com
Then, edit the domain configuration file like below.
# redirect www to non-www
server {
    listen          80;
    server_name     www.domain.com;
    rewrite ^(.*)   http://domain.com$1 permanent;
}

server {
    listen          80;
    server_name     domain.com;
    access_log      /var/log/nginx/domain.com/access.log;
    error_log       /var/log/nginx/domain.com/error.log;
    rewrite_log     on;
    root            /var/www/domain.com/current/public; # Since capistrano links the latest version to current

location / {
        try_files $uri $uri/ /index.php?$query_string; # This is for pagination
    }

    if (!-d $request_filename) {
        rewrite ^/(.+)/$ /$1 permanent;
    }

    location ~* \.php$ {
fastcgi_pass                    unix:/run/php/php7.1-fpm.sock;
        fastcgi_index                   index.php;
        fastcgi_split_path_info         ^(.+\.php)(.*)$;
        include                         /etc/nginx/fastcgi_params;
        fastcgi_param                   SCRIPT_FILENAME $document_root$fastcgi_script_name;
    } 

    # We don't need .ht files since our environment is not an Apache server
    location ~ /\.ht {
        deny all;
    }
}
Hit CTRL + X and Y then Enter. After that, we need to configure php-fpm configuration. Open it first.
$ nano /etc/php/7.1/fpm/pool.d/www.conf
Then edit the parts below. With the configuration below, when the web server creates files, for instance caching files in the storage directory, the owner of it will be the deployer user and the group of it will be www-data.
user  = deployer
group = www-data

listen = /run/php/php7.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666
Hit CTRL + X and Y then Enter. Then restart the nginx and php7.1-fpm services and logout.
$ service php7.1-fpm restart
$ service nginx restart
$ exit

Part IV - Configuring the Git

Reconnect to your server with the previously created deployer account. Then hit the command below, create a SSH key, skip the first question with Enter and write a passphrase then validate it.
$ ssh-keygen -t rsa
After that, you can use simple cat command here if you don't want to install xclip as we previously did in our local machine.
$ cat /home/deployer/.ssh/id_rsa.pub 
Then from your Github or Bitbucket account, from the settings part of your project, hit deployment keys and then add key, moreover, paste your public key and save.

Then add your repository from either Github or Bitbucket to known hosts.
$ git ls-remote -h git@bitbucket.org:USERNAME/PROJECT.git HEAD # Bitbucket
$ git ls-remote -h git@github.com:USERNAME/PROJECT.git HEAD # Github
To complete the git configuration, you will also need your ssh-agent to be working all the time for the handshaking between the github or bitbucket service and your server.
$ nano ~/.bash_profile
Edit it like below.
eval `keychain --eval --agents ssh id_rsa`
Hit CTRL + X and Y then Enter. Logout from the server and then reconnect and hit the command below. It will ask for the passphrase of the previously generated ssh key within your server, write it.

From now on, ssh-agent will take the care of the rest. It will only ask you to rewrite your passphrase again when you restart the remote machine.

Part V - Configuring Capistrano on Your Local Machine

Install Ruby and Capistrano. The commands below take care of the installation, however, don't forget that you should do this on your local machine, not on your server. It would take around 5-10 minutes.
$ sudo apt-get update
$ sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev

$ cd ~
$ git clone git://github.com/sstephenson/rbenv.git .rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ exec $SHELL
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
$ exec $SHELL

$ git clone https://github.com/sstephenson/rbenv-gem-rehash.git ~/.rbenv/plugins/rbenv-gem-rehash
$ rbenv install 2.3.0
$ rbenv global 2.3.0
$ ruby -v # See if ruby is installed successfully

$ echo "gem: --no-ri --no-rdoc" > ~/.gemrc
$ gem install bundler
$ gem install capistrano
$ gem install capistrano-file-permissions
Then within your Laravel project directory, run the command below.
$ cap install
Firstly, edit the Capfile so that you can use the file-permissions plugin which is not included by default.
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/file-permissions'

Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
Then, you will see that Capistrano has generated a deploy folder and deploy.rb file under the config directory. Within the newly created deploy folder, edit the production.rb file like below.
server 'SERVER_IP_ADDRESS',
user: 'deployer',
roles: %w{web db app},
ssh_options: {
user: 'deployer',
  keys: %w(/home/YOUR_USERNAME_IN_YOUR_LOCAL_MACHINE/.ssh/id_rsa),
  forward_agent: true,
  auth_methods: %w(publickey)
}
Then edit the previously generated deploy.rb file like below. You may change the tasks as you wish.
set :application, 'domain.com'
set :repo_url, 'git@bitbucket.org:USERNAME/PROJECT.git' # or -> 'git@github.com:USERNAME/PROJECT.git'
set :deploy_to, '/var/www/domain.com'
set :keep_releases, 3
set :scm, :git
set :log_level, Logger::DEBUG
set :default_stage, "production"
set :linked_dirs, fetch(:linked_dirs, []) + %w{public/uploads} # shared between all releases. for instance, files that are generated by the users

namespace :php7_fpm do
    desc "restarts php7.1-fpm"
    task :restart do
        on roles(:all) do
            execute :sudo, :service, "php7.1-fpm", "restart"
        end
    end
end

namespace :composer do
    desc "Runs composer."
    task :install do
        on roles(:all) do
            within release_path do
                execute "composer", "install", "--no-dev", "--no-interaction", "--prefer-dist"
            end
        end
    end
end

namespace :laravel do

    desc "Laravel Artisan migrate."
    task :migrate do
        on roles(:all) do
            within release_path do
                execute "php", "artisan", "migrate"
            end
        end
    end

    desc "Cleans up the development files."
    task :cleanup do
        on roles(:all) do
            within release_path do
              execute "rm", "composer*"
              execute "rm", "LICENSE"
              execute "rm", "package.json"
              execute "rm", "phpunit.xml"
              execute "rm", "README.md"
              execute "rm", "server.php"
            end
        end
    end
end

namespace :deploy do
    after :publishing, "composer:install"
    after :finishing,  "laravel:cleanup"
    after :finishing,  "deploy:cleanup"
    after :finishing, "php7_fpm:restart"
    after :rollback,  "php7_fpm:restart"
end
Finally, you can deploy your project with the simple command below.
$ cap production deploy
Before I finish,  I should also note that if you are not hosting your .env file on your git repository, connect to your server and create it within your latest release's root path($ nano .env) and then edit it. Then, within your deploy.rb file on your local machine, add the line below, after that you won't need to create it every time.
set :linked_files, %w{.env}
To sum up, in this article, we have set our server from scratch to ready for deploy. After doing all those operations above, you shouldn't face with any issues about deployment.