Bitbucket Pipelines: how to set up automatic deployment for a website

23.01.2023 | Author: Marcin Wiercioch

Wouldn’t it be wonderful if every change made in the repository automatically appeared in the production environment? Web developers have been struggling with this for many years.
There are many platforms, services and solutions on the market that can meet this need to varying degrees. Automation is a very complex topic and can take different forms and scopes. It all depends on how we deliver our web application to end customers. With an extensive system made up of different runtime environments (dev, test, prod and so on), the automation process will look completely different than for a simple WordPress-based website.
Imagine a system that is constantly being developed, where changes should be delivered smoothly to the right environment. On top of that, before deploying changes from the repository, we should run tests every time. We could add one more element: the need to build a Docker image from the sources in the git repository. Replacing the image that serves as the base for containers running, for example, in Kubernetes would be the last piece of the puzzle.
As you can see, this process can be very complicated, and it would be easy to forget something or do something wrong while performing these operations manually one after another. That is exactly why such complex operations are automated. This shortens the time it takes to deliver changes and reduces errors and faults.

In this article we will not build a configuration for a process as complex as the one described above. We will focus on the basics that let us take care of automation on a smaller scale.

The web application we will build the environment for

First we need to set a goal, so we should think about what we want to automate and how. Our example will be a WordPress-based website. The starting point is the version control system, git. We cannot start without it, because practically all modern software development models are based on git or another version control system.
In this article we will build the configuration with Bitbucket Pipelines. However, similar solutions exist at competing providers (GitHub Actions, GitLab and so on).
The website we are building and developing runs on a server in the classic LAMP model. We will publish updates with git by pulling the latest changes. So that our example is not too simple, I suggest we work with two branches: master and dev. We will have two runtime environments. The first will be the development version of the website, where new features are developed and changes are made. The second environment will act as production. The mapping between git branches and environments will therefore look like this:

  • master -> production
  • dev -> development

The beginning: git hooks

Before we focus on the solutions Bitbucket provides, let’s stop for a moment and think about how all this is possible. Git has a wonderful feature called git hooks.
It is simply a mechanism that lets you plug your own code into specific events in the repository (before a commit, after a commit and so on). Many years ago, the first automation mechanisms were built on this. Today is no different: popular source code versioning platforms are also based on it. Of course, it is now wrapped in advanced mechanisms at various levels, but the trigger for further actions is still a git hook.

Bitbucket Pipelines

Let’s assume we have a repository hosted on Bitbucket. If we do not have a dev branch yet, we should create one (git checkout -b dev). Now it is time to explain what these magical pipelines are. They are simply a sequence of instructions formatted in a YAML file and executed in specific situations. You should know that everything executed in a pipeline runs inside a container built from a specified Docker image. Bitbucket Pipelines is integrated with Docker Hub, so we can use our own images.

To think about automation, we need to configure a few things in our repository settings. First we have to enable pipelines in our repository. This option is disabled by default.

Enabling Bitbucket Pipelines in a Bitbucket repository

The next step is to set up the runtime environments. In the repository settings, go to the pipelines -> deployments tab. There you will see a screen with the default environments (test, stage, prod). In our simplified model we will use only two: dev and prod. As you can see, we need to add the dev environment. To keep things tidy, let’s add a new item to the staging section.

Configuring runtime environments in Bitbucket Pipelines

 

The bitbucket-pipelines.yml configuration file

The heart of the mechanism is a special file that has to be stored in the repository. Once the file is added, our instructions will be executed by Bitbucket. Before we do that, though, let’s look at the syntax and the basic building blocks.

Image

The top-level element that will always be with us is “image”. This is where we specify the name of the Docker image that becomes the basis for creating the container. Our instructions run inside this container. We can prepare our own images with software already installed and configured, or use a general-purpose image such as Ubuntu.

Definitions

This element is not always used, because in small projects instructions are not grouped into definitions. Definitions are divided into steps, which in turn contain specific commands to run. Using this clause helps you keep things readable and means some steps do not have to be repeated.

Pipelines

Under this keyword we configure the actions to be performed. It comes down to setting the order of consecutive steps, which may have been described earlier in the definitions section. Operations can be run separately for each branch of the git repository.

Clone

Before each step, Bitbucket performs a git clone, that is, it fetches a fresh copy of the git repository for the procedure being run. This makes sense, because tests are most often run on the code in the repo. However, we do not always need it, and sometimes it can even get in the way. An example is a deploy that tries to launch a new version of the application based on processes from previous steps. That is why the clone clause lets us control this behaviour at the pipeline configuration level.

An example configuration that deploys a website

Below is an example of a complete bitbucket-pipelines.yml file. When we add it to the root directory of the repository with the “pipelines” option enabled in the configuration, Bitbucket will start the deployment process with every commit.

definitions:
  steps:
    - step: &deploy-to-prod
        name: Deploy to Production
        image: ubuntu:22.04
        script:
          - php /usr/bin/dep deploy dev
    - step: &deploy-to-dev
        name: Deploy to New
        image: ubuntu:22.04
        script:
          - php /usr/bin/dep deploy new

pipelines:
  branches:
    master:
      - step: *deploy-to-prod
    dev:
      - step: *deploy-to-dev

Let’s analyse what this file actually contains. First we have the “definitions” section, which contains a list of defined steps. In our case there are two. The first is for the production environment and the second for the dev version. Each step runs the deploy command. To make our work easier, we use “Deployer”, a very popular tool for PHP-based web applications. I encourage you to explore what this project can do: deployer.org.

The next section is “pipelines”, where the previously defined steps are mapped to branches in the git repository.

This simple configuration shows the direction we could take to expand our runtime environments. Bitbucket also lets you use environment variables. All sensitive data, such as passwords, keys and tokens, should be stored in environment variables, so that we can use them in the YAML file. Imagine a scenario where, after updating a website that sits behind a reverse proxy such as Cloudflare, we want the cache to be cleared. Normally we would have to log in to the Cloudflare dashboard and run the right option. Thanks to the pipeline, we do not have to do it manually. We can add this operation to our automation: just use the Cloudflare API, store the access key in an environment variable and call the right endpoint of the REST API.

Process time in the cloud, or there is no such thing as a free lunch 🙂

There is one more aspect of automation with Bitbucket Pipelines. Processes executed in the Bitbucket cloud use server resources, so there are costs. Bitbucket charges based on the time spent processing our jobs. For a small project we will probably fit within the free limit, which is set at a few dozen minutes a month. If our needs are greater, we have to consider a paid plan.

At first glance, Bitbucket Pipelines may seem complicated. Keep in mind that the world of automation is only simple once everything has been configured correctly, and the work itself requires experience. Even so, the time invested at the start pays back many times over.

Related technologies

Marcin Wiercioch Marcin Wiercioch

full stack developer

Co-founder of Okinet, PHP developer, full stack developer, Linux administrator and technology enthusiast with 20 years of experience. Lately I have been focusing especially on optimising and automating development environments, which makes the web applications we build efficient, secure and easy to develop further.

All articles by this author

Share

Rate this article

Let’s talk
about your project

+48 506 160 480
biuro@okinet.pl

or write to us