Automating Linkarooie Deployment with Kamal and GitHub Actions

Prerequisites

  • Existing Kamal setup
  • Basic GitHub Actions knowledge
  • Familiarity with Docker and Redis

Automating Linkarooie Deployment with Kamal and GitHub Actions

This guide will walk you through automating the deployment of your Linkarooie application using Kamal and GitHub Actions. We will focus on using composite actions for setup, deploying both web and Sidekiq services, handling Redis, SSL via Let’s Encrypt, and rolling deploys. Importantly, we will also show how to securely manage all the secrets and environment variables from your deploy.yml.


Step 1: Understanding the deploy.yml

Here’s a breakdown of your Linkarooie deploy.yml configuration:

service: linkarooie
image: loftwah/linkarooie

# Servers for deployment
servers:
  web:
    - <%= ENV['KAMAL_HOST'] %> # Set the KAMAL_HOST as an environment variable

  # Sidekiq server for background jobs
  job:
    hosts:
      - <%= ENV['KAMAL_HOST'] %>
    cmd: bundle exec sidekiq # Command to start Sidekiq

# SSL and proxy configuration
proxy:
  ssl: true # Enable SSL via Let's Encrypt
  host: linkarooie.com # Domain for the app
  app_port: 3000 # The port your app listens to inside the container

# Docker registry credentials
registry:
  server: ghcr.io
  username: loftwah
  password:
    - KAMAL_REGISTRY_PASSWORD

# Specify the builder architecture
builder:
  arch: amd64

# Environment variables
env:
  clear:
    KAMAL_HOST: <%= ENV['KAMAL_HOST'] %> # Host for the deployment
    REDIS_URL: redis://redis-linkarooie:6379/0 # URL for Redis
  secret:
    - KAMAL_REGISTRY_USERNAME
    - KAMAL_REGISTRY_PASSWORD
    - SECRET_KEY_BASE
    - AXIOM_API_KEY
    - DO_TOKEN
    - SPACES_REGION
    - SPACES_BUCKET_NAME
    - SPACES_BUCKET_CONTENT
    - SPACES_ACCESS_KEY_ID
    - SPACES_SECRET_ACCESS_KEY
    - RAILS_MASTER_KEY

# Volumes for persistent storage
volumes:
  - data_storage:/rails/storage

# Accessories for additional services
accessories:
  redis:
    service: linkarooie
    image: redis:6-alpine
    host: <%= ENV['KAMAL_HOST'] %>
    volumes:
      - redis_data:/data
    options:
      name: redis-linkarooie

# Rolling deploys
boot:
  limit: 10 # Number of servers to restart at once
  wait: 2 # Seconds to wait between restarts

# Aliases for common commands
aliases:
  shell: app exec --interactive --reuse "bash"

Key Points to Note:

Now, let’s move on to GitHub Actions to automate the deployment.


Step 2: Setting Up GitHub Actions for Deployment

A. Composite Action for Setup

We begin by setting up Ruby, Docker, and SSH. This composite action ensures that you don’t repeat these steps across different workflows.

Create a file at .github/workflows/setup/action.yml:

name: Setup Environment

inputs:
  ssh-private-key:
    description: SSH Private Key
    required: true

runs:
  using: "composite"
  steps:
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version-file: .ruby-version

    - name: Set up Docker
      uses: docker/setup-buildx-action@v3

    - name: Set up SSH agent
      uses: webfactory/ssh-agent@v0.9.0
      with:
        ssh-private-key: ${{ inputs.ssh-private-key }}

This reusable composite action will be called by both the automatic and manual deployment workflows.


B. Composite Action for Kamal Deploy

This composite action handles the actual deployment using Kamal. It will be used to deploy both the web service and the Sidekiq service.

Create the file .github/workflows/kamal-deploy/action.yaml:

name: Kamal Deploy

inputs:
  environment:
    description: Deployment environment (e.g., production, staging)
    required: true
  kamal-registry-password:
    description: Kamal Docker Registry Password
    required: true
  database-url:
    description: Database URL
    required: true
  redis-url:
    description: Redis URL
    required: true
  rails-master-key:
    description: Rails Master Key
    required: true
  secret-key-base:
    description: Rails Secret Key Base
    required: true
  axiom-api-key:
    description: Axiom API Key
    required: false
  do-token:
    description: DigitalOcean Token
    required: false
  spaces-region:
    description: Spaces Region
    required: false
  spaces-bucket-name:
    description: Spaces Bucket Name
    required: false
  spaces-access-key-id:
    description: Spaces Access Key ID
    required: false
  spaces-secret-access-key:
    description: Spaces Secret Access Key
    required: false

runs:
  using: "composite"
  steps:
    - name: Deploy with Kamal
      shell: bash
      run: |
        ./bin/kamal deploy --destination=${{ inputs.environment }}
      env:
        KAMAL_REGISTRY_PASSWORD: ${{ inputs.kamal-registry-password }}
        DATABASE_URL: ${{ inputs.database-url }}
        REDIS_URL: ${{ inputs.redis-url }}
        RAILS_MASTER_KEY: ${{ inputs.rails-master-key }}
        SECRET_KEY_BASE: ${{ inputs.secret-key-base }}
        AXIOM_API_KEY: ${{ inputs.axiom-api-key }}
        DO_TOKEN: ${{ inputs.do-token }}
        SPACES_REGION: ${{ inputs.spaces-region }}
        SPACES_BUCKET_NAME: ${{ inputs.spaces-bucket-name }}
        SPACES_ACCESS_KEY_ID: ${{ inputs.spaces-access-key-id }}
        SPACES_SECRET_ACCESS_KEY: ${{ inputs.spaces-secret-access-key }}

This action will deploy the web and Sidekiq services and configure Redis as defined in your deploy.yml.


C. Automatic Deployment Workflow

Now, let’s set up a workflow that triggers automatic deployment whenever you push to the main branch.

Create the file .github/workflows/01.deploy_production.yaml:

name: Deploy to Production

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Environment
        uses: ./.github/workflows/setup/action.yml
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy with Kamal
        uses: ./.github/workflows/kamal-deploy/action.yaml
        with:
          environment: production
          kamal-registry-password: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
          database-url: ${{ secrets.DATABASE_URL }}
          redis-url: ${{ secrets.REDIS_URL }}
          rails-master-key: ${{ secrets.RAILS_MASTER_KEY }}
          secret-key-base: ${{ secrets.SECRET_KEY_BASE }}
          axiom-api-key: ${{ secrets.AXIOM_API_KEY }}
          do-token: ${{ secrets.DO_TOKEN }}
          spaces-region: ${{ secrets.SPACES_REGION }}
          spaces-bucket-name: ${{ secrets.SPACES_BUCKET_NAME }}
          spaces-access-key-id: ${{ secrets.SPACES_ACCESS_KEY_ID }}
          spaces-secret-access-key: ${{ secrets.SPACES_SECRET_ACCESS_KEY }}

D. Manual Deployment Workflow

For manual deployments, you can trigger them directly from GitHub’s interface.

Create the file .github/workflows/02.deploy_manually.yaml:

name: Deploy Manually

on:
  workflow_dispatch:
    inputs:
      environment:
        description: Deployment Environment
        required: true
        default: "production"
        type: choice
        options:
          - production
          - staging

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4



 - name: Setup Environment
        uses: ./.github/workflows/setup/action.yml
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Deploy with Kamal
        uses: ./.github/workflows/kamal-deploy/action.yaml
        with:
          environment: ${{ github.event.inputs.environment }}
          kamal-registry-password: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
          database-url: ${{ secrets.DATABASE_URL }}
          redis-url: ${{ secrets.REDIS_URL }}
          rails-master-key: ${{ secrets.RAILS_MASTER_KEY }}
          secret-key-base: ${{ secrets.SECRET_KEY_BASE }}
          axiom-api-key: ${{ secrets.AXIOM_API_KEY }}
          do-token: ${{ secrets.DO_TOKEN }}
          spaces-region: ${{ secrets.SPACES_REGION }}
          spaces-bucket-name: ${{ secrets.SPACES_BUCKET_NAME }}
          spaces-access-key-id: ${{ secrets.SPACES_ACCESS_KEY_ID }}
          spaces-secret-access-key: ${{ secrets.SPACES_SECRET_ACCESS_KEY }}

Step 3: Managing Secrets and Environment Variables

As per your deploy.yml, the following secrets and environment variables need to be securely managed:

Secrets to Add in GitHub

You can add these secrets in GitHub by going to Settings → Secrets → Actions.


Conclusion

This complete guide ties your Linkarooie deploy.yml directly into the GitHub Actions workflows, including:

Now your deployments are fully automated and aligned with the configuration you’ve set up in Kamal.


Acknowledgments

A special thank you to Igor Alexandrov for creating and maintaining Kamal GitHub Actions, which made automating the deployment of Linkarooie much easier.