Capistrano Deployment with Rails
capistranorb is an awesome tool for automation and deploying applications across multiple servers. Rails applications can also be easily deployed using capistrano
. This post assumes you already have capistrano deployment up and running for rails application. Rails supports multiple environments and capistrano can deploy to multiple environments too. To deploy to environments staging
or production
following capistrano commands can be used:
bundle exec cap staging deploy:check # to run a dry run or test run of the deployment
bundle exec cap staging deploy # to actually deploy
Deploying with Github Actions
Github Actions offers features to trigger workflows with a variety of events. You should be familiar with the syntax for Github Actions Syntaxes. You can use variety of events to trigger deployment workflow like pull_request
, push
, and workflow_dispatch
. We will configure push
event to deploy application for two branches staging
and master
. staging
branch is for staging
environment and master
branch is for production
environment. We will have following steps configured:
- find environment to be used from branches
- send deployment started slack notification
- add key to ssh agent
- checkout to current branch
- setup ruby
- deploy check using capistrano
- deploy using capistrano
- success notification in slack [ conditional ]
- failure notification in slack [ conditional ]
- send honeybadger deploy event
Find Environment
We will have environment value used across our github actions configuration. This step will determine what is the environment to which the current branch is being deployed to and sets the environment value as the output of the step. Github Actions support setting output of one step to be used to another step. We can do that with some shell command like below:
# find and set environment based on current github branch name
# set-oputput is used to set some variable as output of a step, that can be used later
# for more info: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
- name: Find Environment
id: find_env
run: |
if [ ${{ github.ref_name }} == "master" ]; then
echo "::set-output name=environment::production"
else
echo "::set-output name=environment::staging"
fi
The environment value can be used with ${{ steps.find_env.outputs.environment }}
in the github actions workflow.
Send Deployment Started Slack Notification
We will use voxmedia/github-action-slack-notify-build@v1
action to send notifications to slack.
- name: Send Deployment Started Slack Notification
id: slack
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy STARTED by ${{ github.actor }}
color: warning
${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
expects you have stored SLACK_NOTIFICATIONS_BOT_TOKEN
in the repository or organization level secrets. And similary for SLACK_NOTIFICATIONS_CHANNEL_NAME
.
Add key to ssh-agent
Deploy keys is needed to execute commands in our remote servers where the application is being hosted. Store the deploy keys in the repository secret in KEYS_FOR_DEPLOYMENTS
secret.
- name: Add key to ssh agent
uses: webfactory/ssh-agent@v0.5.4
with:
ssh-private-key: ${{ secrets.KEYS_FOR_DEPLOYMENTS }}
Checkout to current branch
- uses: actions/checkout@v2
Setup Ruby
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
This step uses ruby/setup-ruby@v1
action. This action reads the ruby version to be installed from the file .ruby-version
inside the repository.
Deploy Check
- name: "Deploy check ${{ steps.find_env.outputs.environment }}"
run: |
bundle exec cap ${{ steps.find_env.outputs.environment }} deploy:check
Deploy
- name: "Deploy ${{ steps.find_env.outputs.environment }}"
run: |
bundle exec cap ${{ steps.find_env.outputs.environment }} deploy
Success Notification in Slack
- name: Notify slack success
if: success()
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy SUCCESS by ${{ github.actor }}
color: good
the if: success()
statement will check if the above steps passed or failed. If passed will send notification to slack.
Failure Notification in Slack
- name: Notify slack fail
if: failure()
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy FAILED by ${{ github.actor }}
color: danger
Honeybadger Notification
- name: Send Honeybadger Notification
uses: honeybadger-io/github-notify-deploy-action@v1
with:
api_key: ${{ secrets.HONEYBADGER_API_KEY }}
environment: ${{ steps.find_env.outputs.environment }}
The final github actions workflow file looks like this:
name: Deploy API to Server
on:
push:
branches: [ staging, master ]
jobs:
deploy:
name: "Deploy ${{ github.ref_name }}"
runs-on: [ self-hosted, "${{ github.ref_name }}", api ]
steps:
# find and set environment based on current github branch name
# set-oputput is used to set some variable as output of a step, that can be used later
# for more info: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
- name: Find Environment
id: find_env
run: |
if [ ${{ github.ref_name }} == "master" ]; then
echo "::set-output name=environment::production"
else
echo "::set-output name=environment::staging"
fi
- name: Send Deployment Started Slack Notification
if: success()
n id: slack
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy STARTED by ${{ github.actor }}
color: warning
- name: Add key to ssh agent
uses: webfactory/ssh-agent@v0.5.4
with:
ssh-private-key: ${{ secrets.KEYS_FOR_DEPLOYMENTS }}
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: "Deploy check ${{ steps.find_env.outputs.environment }}"
run: |
bundle exec cap ${{ steps.find_env.outputs.environment }} deploy:check
- name: "Deploy ${{ steps.find_env.outputs.environment }}"
run: |
bundle exec cap ${{ steps.find_env.outputs.environment }} deploy
- name: Notify slack success
if: success()
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy SUCCESS by ${{ github.actor }}
color: good
- name: Notify slack fail
if: failure()
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_NOTIFICATIONS_BOT_TOKEN }}
uses: voxmedia/github-action-slack-notify-build@v1
with:
channel: ${{ secrets.SLACK_NOTIFICATIONS_CHANNEL_NAME }}
status: Deploy FAILED by ${{ github.actor }}
color: danger
- name: Send Honeybadger Notification
uses: honeybadger-io/github-notify-deploy-action@v1
with:
api_key: ${{ secrets.HONEYBADGER_API_KEY }}
environment: ${{ steps.find_env.outputs.environment }}
Because I am running this workflow from a self-hosted
runner, I have added the labels self-hosted, "${{ github.ref_name }}", api
to the runs-on
key.
runs-on: [ self-hosted, "${{ github.ref_name }}", api ]
github.ref_name
returns the current branch. This yml
file must be located inside .github/workflows/<your_file_name>.yml
, so that github actions runners can pick this up and run the workflow.