Crash Course: Continuous Deployment with Semaphore CI

Viraj Khatavkar

Software is playing an ever bigger role in how companies compete across a broad range of industries. Large organizations are finding that their current approaches to managing software are limiting their ability to respond as quickly as business requires. Continuous delivery helps with this.

Semaphore CI logo

Understanding continuous delivery, a now already long-standing buzzword, is not a problem, but implementing it in the right way has been a challenge for quite a few of us, and one we’ll discuss in this post.

Getting Started

We can easily upload our files to a server using an open source desktop client like Filezilla. Those who have used this are aware that this process is cumbersome and irritating as it doesn’t let us automate the deployment process, and we always end up having to upload the whole project, even if we have modified only a part of it. Alternatively, we could do a git pull on the server and our application is instantly in its latest state, but this workflow doesn’t work in today’s world where we have to continuously deliver software to our end users bug-free.

In this article, we will cover the process of deploying a Laravel application on a Digital Ocean server through a continuous delivery pipeline using Semaphore. The source code for the application is present on Github.

Set up a Project on Semaphore

First, create a Semaphore account. After signing up, we see a page to create a new project.

Create Project

We then have to select the account where our repository is present. If you haven’t connected your source code repository provider with Semaphore, you can do so in this step. Semaphore natively supports Bitbucket and Github.

Select Account

If you forked the aforementioned repo, you can select it in this step:

Select Repository

We will select the master branch for our case:

Select Branch

Once we select a branch, Semaphore will start analyzing the project.

Analyzing Project

Semaphore will try to clone the repository on their server and fetch some meta information to get started. For our example project, the following meta information was identified:

Result after analyzing project

We will select PHP version 7 as our preferred PHP version and move on.

Configure Builds

We will modify the above settings to best suit our project. Please check the settings thoroughly before moving forward with a build to avoid errors.

Our First Build

After configuring the appropriate settings, we can click Build in the above screen. The build’s progress will be printed on the screen.

Build Progress

Oops! Our build fails due to failing tests. This indicates that there is a problem with our code.

Build Failed

In this case, the problem lies in the configuration of our build. A typical Laravel application requires an environment file at its root with the necessary configuration parameters. We haven’t configured the file in our project yet. Let us create an environment file at the root of our project.

Go to Project Settings -> Configuration files and create the environment file for our project as follows:

Configuration File

We can either push to the master branch or rebuild from the Semaphore UI itself to start our build process again. For now, we will initiate a rebuild from the UI itself by clicking on the Rebuild button on the right-hand side of the last failed build.

Rebuild from UI

Semaphore will start rebuilding our repository by installing the necessary dependencies and running tests with PHPUnit. We can see that our build is passing sucessfully.

Build Passed

Parallelize Your Builds

Massive test groups are a fact of any above-average sized project. The downside of having such impressive test coverage is that it takes a massive amount of time to run the build. In such scenarios, parallelization is essential.

The fundamentals behind parallelization are to divide our test suites into smaller groups which can be run in parallel. You can divide into groups on the basis of similar functional requirements or independent services or any other logical parameter that best suits your project. I would prefer to group these into similar functional requirements.

In our application, I have written two simple test classes for the Login Page and the Register Page. Let us run parallel test builds in Semaphore. Below is the code for these two tests:


class LoginTest extends TestCase
     * A basic functional test example.
     * @return void
    public function testLoginPage()
             ->see('Login Now');

class RegisterTest extends TestCase
     * A basic functional test example.
     * @return void
    public function testRegisterPage()
             ->see('Register Now');

Now we can configure a parallel job in our build settings to run these two tests separately. You can learn more about parallel test configuration in Semaphore’s documentation.

Parallel Jobs Build Settings

Let’s push to the master branch to trigger the build process.

git push origin master

Once we push to master, Semaphore will automatically detect the change and trigger our build with parallel jobs.

Parallel Builds

Our tests are executed in parallel as shown in the screenshot above.

Continuous Deployment

Finally, let’s set up the deployment process for our project. We will deploy our repository to a Digital Ocean server (make sure you set up a Droplet with the server pointed to home/USER/default before proceeding – good tutorial here). Let’s go through each of the steps:

We will select a Generic Deployment option as we will run our own custom commands to deploy.

Select Deployment

We can select either “Automatic” or “Manual” for deployment strategy. We will select “Automatic Deployment”. This will trigger an automatic deployment whenever we push to our master branch and the build passes.

Automatic Deployment

Here, we need to select the branch we wish to deploy. We will select the master branch.

Deployment Branch

We will enter our custom deployment commands that we want Semaphore to execute for us. The first command gathers the public SSH host keys of our server(s) to avoid an authorization prompt during deployment.

Deployment Commands

Paste the private key of your deployment user in the text area given below. This will allow Semaphore to access our Digital Ocean server and execute the deployment commands.

Private Key for Server

Enter your desired server name and continue:

Server Name

We have set up our deployment server successfully. Let’s initiate a manual deployment once to make sure that everything runs smoothly.

First Deploy

Congratulations! The first deployment has been completed without any errors.

Deployment Successful

If we go to the actual website, we can see that the application has been deployed:

Website deployed

Note: If your build fails, then any deployment configured to be run on your servers will not be triggered. Deployment is triggered only after a successful build.

Henceforth, whenever you push to your master branch, Semaphore will build your project, run your tests and deploy automatically. Let’s do a test push and see the process.

Automatic Build and Deploy

Our project was built successfully and deployed to our server.

Open Source Alternatives

Semaphore is a paid service, but gives a good bang for your buck. If paying for CD isn’t in your budget right now, Gitlab is one open source alternative to the Semaphore platform. The functionality is more or less identical, with the downside being that you have to set everything up manually, and performance can be a serious issue.


We now have a PHP application deployed to a Digital Ocean server with a continuous delivery pipeline in place. Whenever you push to a specific branch or someone sends a pull request, the build will be triggered automatically. Semaphore will handle all the hassles for you and let you know the status of your build.

Note that this is just a simple crash course into the above concepts – building assets like JS and CSS, and zero-downtime deployment is also possible with both Semaphore and custom solutions, and something we’ll cover in a future post. In the meanwhile, you can look into our past tutorial about Deployer – a tool that can assist the above stack in achieving that.

How do you deploy your PHP projects? Have you used Semaphore? How do you feel about it? Using any other free or paid alternatives? Let us know!