Deploy websites with Bitbucket pipelines and Git-ftp

My life got so much easier since I started using pipelines for deployment. I use Bitbucket pipelines quite a lot as most of my private repositories are listed there and the pipelines are super easy to set up.

This tutorial will teach you how to set up Bitbucket pipelines with Git-ftp to deploy websites from Bitbucket repository to FTP hosting.

Enable pipelines for your repository

Go to the repository settings on bitbucket.org. You can enable pipelines under Pipelines Settings.

Add repository variables

Under Pipelines Repository variables you can define variables to use in your pipeline file. Add these three variables: FTP_USERNAME, FTP_PASSWORD and FTP_HOST

Configure bitbucket-pipelines.yml

Create a file with the name bitbucket-pipelines.yml in the root directory of your repository. A simple pipeline using Git-ftp could look like this:

image: wagnerstephan/bitbucket-git-ftp:latest

pipelines:
  custom:
    init:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp init -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST
    deploy:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST --all
  branches:
    master:
    - step:
        name: Deploy production
        deployment: production
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST

Commit this file.

This pipeline uses the docker image wagnerstephan/bitbucket-git-ftp:latest which adds Git-ftp to Atlassians default image version 2.

With this configuration, every time you commit or merge into the master branch the pipeline will deploy. If you'd rather want to only trigger deployments manually, remove the branches part in bitbucket-pipelines.yml.

We are running git reset --hard before pushing with Git-ftp. In some cases the repository could become dirty during the deployment and installation process and I found this to work best to prevent the pipeline from failing. If you find a better solution, please let me know in the comments below.

Initialize Git-ftp

Once you committed the bitbucket-pipelines.yml file, your first pipeline will start running. It will fail though, because first we need to initialize Git-ftp. To do that, go to the "Commits" page in your bitbucket.org repository. Choose the latest commit, then on the right hand side of that page you'll find a "Run pipeline" link with which you can manually trigger pipelines. Select "custom: init" and wait until the pipeline is finished.

Your pipeline is now set up.

Deploy a specific commit

Sometimes you might need to deploy a specific commit. This works very similar to the initialization step: Go to the "Commits" page in your bitbucket.org repository. Choose the commit you'd like to deploy, click on "Run pipeline" and select "custom: deploy-all".

Deploy to multiple development environments

At this stage your pipeline will always run when you commit or merge into your master branch. A more advanced approach would be to have multiple environments with different deployments.

Development, Staging, Production
For the purpuse of this tutorial, we will set up a development architecture with the environments development, staging and production. The development environment will be locally on your workstation only, staging and production will deploy to a hosted server.

Create git branches

Additionally to the master branch, create the branches develop and staging.

develop branch
This branch will not deploy. Develop and test new features locally on this branch. When a new feature is ready for deployment, merge it into staging.

staging branch
The purpose of the staging branch is to have a clone of the production environment where you can run tests or show new features to a customer before they go live.

master branch
The master branch is your production environment. When everything runs smoothely on staging, merge it into master to trigger the production pipeline.

Add repository variables

Create three more repository variables for the staging envoironment: FTP_USERNAME_STAGING, FTP_PASSWORD_STAGING, FTP_HOST_STAGING

Configure bitbucket-pipelines.yml

Edit or create the bitbucket-pipelines.yml file in the root directory of your repository by adding the staging branch:

image: wagnerstephan/bitbucket-git-ftp:latest

pipelines:
  custom:
    init-staging:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp init -u "$FTP_USERNAME_STAGING" -p "$FTP_PASSWORD_STAGING" ftp://$FTP_HOST_STAGING
    init-production:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp init -u "$FTP_USERNAME_PRODUCTION" -p "$FTP_PASSWORD_PRODUCTION" ftp://$FTP_HOST_PRODUCTION
    deploy-staging:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME_STAGING" -p "$FTP_PASSWORD_STAGING" ftp://$FTP_HOST_STAGING --all
    deploy-master:
    - step:
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST --all
  branches:
    staging:
    - step:
        name: Deploy staging
        deployment: staging
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME_STAGING" -p "$FTP_PASSWORD_STAGING" ftp://$FTP_HOST_STAGING
    master:
    - step:
        name: Deploy production
        deployment: production
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME" -p "$FTP_PASSWORD" ftp://$FTP_HOST

Your pipelines are now set up. If it's the first time a pipeline runs on one of the branches, remember to manually trigger the initialization process.

Run a bash script after deployment

Sometimes it makes sense to run a bash script on your server after deployment, e.g. to install dependencies or delete caches. Read Atlassians Use SSH keys in Bitbucket Pipelines tutorial. Once your pipeline can connect to your server via SSH, you can add a deploy.sh file to your project and execute it by adding following command to the pipeline:

    master:
    - step:
        name: Deploy production
        deployment: production
        caches:
          - node
        script:
          - npm install
          - npm run gulp
          - npm run test
          - git reset --hard
          - git ftp push -u "$FTP_USERNAME_PRODUCTION" -p "$FTP_PASSWORD_PRODUCTION" ftp://$FTP_HOST_PRODUCTION
          - ssh -t username@$REMOTE_ADDR "cd ./httpdocs/path_to_deploy_file/ && ./deploy.sh"

Notice that we added a new variable REMOTE_ADDR which should be added to your repository variables.

Attribution

The bitbucket-pipelines.yml file was inspired by https://gist.github.com/mcnamee/aa141af.