This is a complete walk-through on how to deploy a Rails 6 application using the Bitbucket Pipeline to a remote server – in this tutorial, DigitalOcean.

Prerequisites:

  1. You have an account with Bitbucket
  2. You have a newly/fresh installation of Ruby on Rails 6
  3. You've used the 1-click install Ruby on Rails

Although I say "a complete walk-through", by that I mean for the deployment process. Go ahead and have those prerequisites at hand then come back and continue reading.

Our pipeline should distribute the final package with all node_modules and gem files already downloaded and install. On our server, all we need to do is to restart our server. This tutorial won't go into server restart.

Pipeline - bitbucket-pipelines.yml

# This is a sample build configuration for Ruby, the default yml file.
# Check our guides at https://confluence.atlassian.com/x/8r-5Mw for more examples.
# Only use spaces to indent your .yml configuration.
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
# https://hub.docker.com/_/ruby/
image: ruby:2.6.0

pipelines:
  branches:
    master:
      - step:
          # Creates a production-quality bundled (artifact) of everything needed 
          # to deploy to staging or live
          name: Package
          deployment: test
          caches:
            - node
          artifacts:
            - dist.tgz
          script:
            # Export the git repo to a dist folder
            - git archive --format=tar --prefix=dist/ --output=../dist.tar HEAD
            - tar xvf ../dist.tar -C ../

            # Change dir to install files 
            - cd ../dist

            # Change bundle location by creating a bundle config file
            # inside our rails project instead of using the systems's
            - mkdir .bundle && touch .bundle/config && echo BUNDLE_PATH:' "vendor/bundle"' >> .bundle/config
            # Install bundler
            - gem install bundler:2.0.1
            # Install all gems inside the dist folder to the location
            # vendor/bundle of our rails app
            - bundle install --path vendor/bundle

            # Install node as it's not available in the ruby image
            - apt-get -qq install gnupg
            - curl -sL https://deb.nodesource.com/setup_10.x | bash -
            - apt-get install -y build-essential
            - apt-get install -y nodejs
            - apt-get update

            # Install the node dependencies
            # This creates the node_modules folder
            - npm install
            
            # prune dev npm packages (leaving only production, if any exist)
            - npm prune --production

            # Create the deploy artifact and move it into the BITBUCKET_CLONE_DIR 
            # so it can be captured as an artifact
            - cd ../
            - tar zcf ${BITBUCKET_CLONE_DIR}/dist.tgz dist

      - step:
          # Copies the Package step artifact over to server,
          # with minor cleanup (delete)
          # NO gem is installed on server. Download file has all files needed
          name: Deploy to Staging
          deployment: staging
          trigger: manual
          script:
            # Strip the branch name of problematic chars
            - export BRANCH_NAME=${BITBUCKET_BRANCH//[\/\`\;\"\'\@\&\>\<\!\|\*]/_}
            # Calculate the name of the deploy
            - export DEPLOY_NAME=${BITBUCKET_REPO_SLUG}-${BRANCH_NAME:0:35}-${BITBUCKET_BUILD_NUMBER}
            # rename the artifact to the deploy name
            - mv dist.tgz ${DEPLOY_NAME}.tgz
            # copy the artifact to the downloads section of the repo as a deploy
            - curl -s -u ${ZIRED_UPLOAD_AUTH} -X POST "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" -F files=@"${DEPLOY_NAME}.tgz"
            # Run the script to pull the deploy from the downloads section of the repo
            - echo "Downloading '${DEPLOY_NAME}' 😉"
            - ssh rails@${FTP_HOST} ./fetchDeploymentArtifact.sh "${ZIRED_UPLOAD_AUTH}" "${BITBUCKET_REPO_OWNER}" "${BITBUCKET_REPO_SLUG}" "${DEPLOY_NAME}.tgz"
            # trigger the script to prepare the deploy and push it to the live servers
            - echo "Deploying '${DEPLOY_NAME}' to staging 😬"
            # TODO: use grep to get ruby version from .ruby-version file
            # grep -m1 "" .ruby-version OR set an env variable with version number
            - RUBY_VERSION=2.6.0
            - ssh rails@${FTP_HOST} ./deployPipelineArtifact.sh "${DEPLOY_NAME}.tgz" "${RUBY_VERSION}"
            
definitions:
  caches:
    bundler: ./vendor
    

A lot of things happening in that yaml file! 😬 Let's break it down. On each git push, an Artifact is created – the "Package" step. During this process, all dependencies are installed including gems and node modules. With the same process as we did when installing the node modules, we did with all gem files. This time, gems are installed to vendor/bundle and we added a hidden bundler config file at .bundle/config. The . means it's a hidden folder. You can read more about bundler here.

Step 2 is essentially server actions such as saving a copy of the artifact to the downloads section of your repository, downloading the artifact to your server then extract the files to the correct folder location. The only thing that's need to be done is to restart the server. I've left that out. With this setup, you will definitely consume build time. To decrease build time, you may want to have a look at Docker Hub where you could create a standard/default custom ruby image with, probably, all the required gems already installed etc.

fetchDeploymentArtifact.sh

#!/bin/bash

ZIRED_UPLOAD_AUTH=$1
BITBUCKET_REPO_OWNER=$2
BITBUCKET_REPO_SLUG=$3
DEPLOY_NAME=$4

# echo ${ZIRED_UPLOAD_AUTH}
# echo "${BITBUCKET_REPO_OWNER}"
# echo "${BITBUCKET_REPO_SLUG}"
# echo "${DEPLOY_NAME}"

# Downloads the packaged artifact from the repo's download folder
curl -u "${ZIRED_UPLOAD_AUTH}" -O -L "https://@api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads/${DEPLOY_NAME}"

deployPipelineArtifact.sh

#!/bin/bash

DEPLOY_NAME=$1
# ie 2.6.0
RUBY_VERSION=$2
# Unzip/untar files to correct location then delete downloaded .tgz file
tar zxvf "${DEPLOY_NAME}" -C ./zired-app && rm -rf "${DEPLOY_NAME}"
# Install ruby with rvm to the system's directory
# If you use rbenv:
# rbenv install "${RUBY_VERSION}"
# This my take some time. I would rather you install this on server
# directly as this will increase build time
rvm install "ruby-${RUBY_VERSION}"

Both files should be created manually on your server. After, use chmod u+x <filename> to give it ssh permissions. More about bash scripts here.

As with a restart, you may also need a script that runs rails db:migrate, depending on your setup, to migrate any generated migration files. 🤷🏽‍♂️ There is room for refactor here such as test cases during build time. This is my first attempt for CI/CD ✌🏼

Questions/refactor? See Twitter feed.