Posted Saturday, 9th May 2020

Basic deployment of a Laravel application using GitLab and SSH

We don't always need an extremely complicated deployment process for a our Laravel applications. Sometimes, a simple git pull origin master and composer install will do the trick.

By the end of this article, I will explain how to set up and automate the deployment of your master branch over SSH and install any dependencies that your application requires. Once we have the foundation down, you will be able to expand upon your .gitlab-ci.yml to satisfy your deployment needs.

We're not going to go into detail about how GitLab or SSH keys work as these are beasts of their own. However, we will touch these subjects at a high level. We are also going to assume that you have your application up and running on a server that you can access.

This is very basic approach and only includes the deployment script. GitLab CI/CD allows you to do so much more.

GitLab

GitLab offers a very generous 2,000 CI pipeline minutes per month on their shared runners for free. For a lot hobbyist and developers, this will be more than enough to get them through each month.

Setting up SSH access

Before we can set up our continuous integration, we need to ensure that we have two SSH keys pairs. One which allows your web user to access your GitLab (so that it can pull down any new changes) and one which allows the GitLab runner to access your server (so that we can perform commands on your server from the GitLab runner).

Firstly, generate the SSH key that allows your web user to access GitLab. This be done by copying your web users public key from your server to your GitLab SSH Keys. Log in to your server as your web user and find your public key which is usually located in ~/.ssh/id_rsa.pub and save it to your GitLab SSH Keys.

Secondly, generate the SSH key that allows the GitLab runner to access your server. You will need to add the public key to your servers web user ~/.ssh/authorized_keys file and the private key to your GitLab repository as a CI/CD variablewith the key SSH_PRIVATE_KEY.

gitlab-ci.yml

We can now start playing around with GitLabs powerful CI/CD tools.

The main way you can control GitLab CI/CD is through a file in the root direction of your project called .gitlab-ci.yml. This file tells GitLab how to configure your projects piplines.

Lets have a look at our .gitlab-ci.yml file:

image: lorisleiva/laravel-docker

variables:
    SERVER_USER: "webserver"
    SERVER_HOST: "12.321.90.54"
    APP_DIRECTORY: "/var/www/myawesomesite.co.uk/html/"

stages:
  - deploy

# Template to install openssh client and ssh key
.initialise_ssh:
    before_script:
        # Install the ssh-agent
        - 'which ssh-agent || (apt-get update -y && apt-get install openssh-client -y)'

        # Run the ssh-agent
        - eval $(ssh-agent -s)

        # Add the SSH key to the ssh-agent
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -

        # Create the SSH directories
        - mkdir -p ~/.ssh
        - chmod 700 ~/.ssh

        # Use ssh-keyscan to scan the keys of our server
        - ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
        - chmod 644 ~/.ssh/known_hosts

# Deploy to production job
deploy_to_production:
    stage: deploy
    extends:
        - .initialise_ssh
    script:
        # Checkout master and pull the latest changes
        - ssh $SERVER_USER@$SERVER_HOST "cd $APP_DIRECTORY && git checkout master && git pull origin master && exit"

        # Install any dependencies
        - ssh $SERVER_USER@$SERVER_HOST "cd $APP_DIRECTORY && npm install && npm run production && composer install --no-interaction --no-ansi --no-progress --optimize-autoloader --no-dev --no-scripts && exit"
    only:
        - master

This simple config creates a pipeline when code is pushed to the master branch, connects to your server using your SSH key and runs a set of commands to catch your website up with the latest changes.

Lets go through the main parts of the .gitlab-ci.yml and explain what is happening:

image: lorisleiva/laravel-docker

This identifies the docker image to use when creating the GitLab runner. You can specify your own container or use existing ones. We are using a pre-build image that was specifically made for Laravel. This image is built by Loris Leiva.

variables:
    SERVER_USER: "webserver"
    SERVER_HOST: "12.321.90.54"
    APP_DIRECTORY: "/var/www/myawesomesite.co.uk/html/"

Here if the config for our server. You will need to change the SERVER_USER to your servers web user,SERVER_HOST to the IP address or hostname of your server and APP_DIRECTORYto the absolute root path of where your repository is located on the server.

# Template to install openssh client and ssh key
.initialise_ssh:
    before_script:
        # Install the ssh-agent
        - 'which ssh-agent || (apt-get update -y && apt-get install openssh-client -y)'

        # Run the ssh-agent
        - eval $(ssh-agent -s)

        # Add the SSH key to the ssh-agent
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -

        # Create the SSH directories
        - mkdir -p ~/.ssh
        - chmod 700 ~/.ssh

        # Use ssh-keyscan to scan the keys of our server
        - ssh-keyscan $SERVER_HOST >> ~/.ssh/known_hosts
        - chmod 644 ~/.ssh/known_hosts

This is the template that sets up the SSH agent, adds your SSH_PRIVATE_KEY that you provided as a variable and adds your SERVER_HOST to the GitLab runners ~/.ssh/known_hosts file so we don't have to weaken security in order to connect to your server.

# Deploy to production job
deploy_to_production:
    stage: deploy
    extends:
        - .initialise_ssh
    script:
        # Checkout master and pull the latest changes
        - ssh $SERVER_USER@$SERVER_HOST "cd $APP_DIRECTORY && git checkout master && git pull origin master && exit"

        # Install any dependencies
        - ssh $SERVER_USER@$SERVER_HOST "cd $APP_DIRECTORY && npm install && npm run production && composer install --no-interaction --no-ansi --no-progress --optimize-autoloader --no-dev --no-scripts && exit"
    only:
        - master

Lastly, we have the job. This is what will create the pipeline and execute the scripts. We extend the .initalise_ssh template so that the SSH agent is set up for when our scripts run. We then SSH to the server and checkout/pull the latest changes. Now that we have the latest changes, we run a npm install npm run production and composer install.

This is the part you can expand if you wish to add additional steps such as running migrations. For example:

- ssh $SERVER_USER@$SERVER_HOST "cd $APP_DIRECTORY && php artisan migrate && exit"

Now, commit and push your .gitlab-ci.yml to the master branch after updating it to suit your needs and you should see a new pipeline create and execute our scripts.

To conclude

We now have a very basic CI/CD that will run when we push to the master branch. All of our dependencies will automatically update and any changes will be pulled down.

This set up works fine for basic Laravel applications. However, once we start going into the territory of more complex migrations and deployments, we might want to consider a more reboust setup.

Our current set up doesn't have any fail safes, if we deploy and something goes wrong, we will have to manually rollback any actions that have been taken. On top of that, we don't currently cope with the unfortunate event of a bad deployment taking the application offline.

To counter this, we could leverage symlink and deploy to a folder outside our live environment. If the deployment is successful, we switch over the symlink so that our live environment now points to the new code. If the deployment fails, our site wont be affected as it will still be pointed to the old code.

There is a lot of things we can add and change to make our deployment over SSH more robust but there nothing wrong with going back to basic and keeping it simple for some of our more basic projects.

Useful resources