Deploying Capistrano in GitLab CI/CD

Ujan
by Ujan 

Capistrano is a framework for building automated deployment scripts. Although Capistrano is written in Ruby, it can easily be used to deploy projects of any language or framework, whether Rails, Java, or PHP. For more details https://github.com/capistrano/capistrano/

While working on a particular project, we deployed the development environment through docker/ansible, while we used capistrano to deploy the production environment. Deploying to staging and production environments was done manually.

To make it all automatically deploy when the branch is merged to staging or production, we decided to dockerize Capistrano and deploy when the pipeline is triggered. 

  1. Dockerizing

Here is the basic docker sample of a ruby application we used.

FROM ruby:<version_tag>

RUN apk add -U openssh libc6-compat curl ca-certificates build-base git postgresql-dev postgresql-client shared-mime-info yarn

WORKDIR /path/to/workdir

#.ruby-version is required in GemFile
COPY Gemfile Gemfile.lock .ruby-version ./

RUN gem install bundler

RUN bundle lock --add-platform ruby &&  bundle install

COPY . .

CMD sh -c "/bin/sh deploy.sh"

The script deploy.sh actually does the deployment to the server. Let us look into it in a bit of detail.

  1. Deploy script
#!/bin/bash

which ssh-agent || ( apt update -y && apt install openssh-client -y )

eval ssh-agent

chmod 400 key.pem

ssh-add key.pem

ssh-add -l

echo $CI_ENV

cap $CI_ENV deploy

Here, firstly it checks if the ssh-agent command is available. If not, it updates the package list and installs the openssh-client package using apt to make sure the SSH client tools are available in the container. 

Then it starts the SSH agent, which will handle private key authentication for your Git operations. We then need to change the permissions of the keyfile(key.pem).

We then add the key to the SSH agent, allowing it to be used for authenticating with the remote server, and then verify that the key was added successfully.

The echo part is optional but by doing so, we can be sure that the correct environment is deployed to the server. 

Finally, the Capistrano deploy command is deployed to the server in its respective environment. e.g: cap production deploy, the $CI_ENV is passed when the deployment starts.

Now, let us look into implementing it in ci/cd.

  1. .gitlab-ci.yml
stages:
  - deploy


deploy to staging:
    stage: deploy
    only:
        - staging
    before_script:
        - cp $PEM_KEY key.pem
        - docker build -t capistrano-deploy -f Dockerfile-Capistrano .
    script:
        - docker run --rm -e CI_ENV=staging capistrano-deploy

deploy to production:
    stage: deploy
    only:
        - main
    before_script:
        - cp $PEM_KEY key.pem
        - docker build -t capistrano-deploy -f Dockerfile-Capistrano .
    script:
        - docker run --rm -e CI_ENV=production capistrano-deploy

Here, firstly our server keyfile is copied and then docker is built. After finishing building the image, the docker run command will run the container with the appropriate env variables for staging and production according to the branches, and then the container will be removed. 

The main reason behind opting for the shell executor over the Docker executor in GitLab CI/CD was due to the existence of several projects reliant on a centralized GitLab runner. Switching the executor would have had an effect on the pipelines of other projects since some are configured to use the shell executor. Additionally, running both shell and Docker executors on a single runner is not feasible.

Final Words

Gurzu is a full-cycle software development company. Since 2014, we have built for many startups and enterprises from all around the world using Agile methodology. Our team of experienced developers, designers, and test automation engineers can help to develop your next product.

Have a tech idea you want to turn into reality? Book a free consulting callor simply, drop us a message!