DEV Community

Eduardo Lomeli
Eduardo Lomeli

Posted on

Kamal: How to integrate with GitHub Actions using multiple destinations

The recently introduced Kamal tool is very powerful and easy to use, you can achieve full deployment automation when it is integrated into the CI/CD pipelines. I'm going to walk you through how to run it automatically on GitHub Actions with multiple destinations (eg. staging and production).

Pipeline workflows:

Event Action
Push to main (PR merged) Desploy to Staging
Publish release (Tag created) Deploy to Production

Requirements

  • Setup your app secrets in your repo for both environments (staging and production) in this tutorial they are prefixed by STG_ and PROD_.
  • Make sure you have created SSH_PRIVATE_KEY, KAMAL_REGISTRY_USERNAME and KAMAL_REGISTRY_PASSWORD secrets.

GitHub Action files

.github/ ├─ workflows/ ├─ job-deploy-kamal.yml ├─ workflow-deploy-production.yml ├─ workflow-deploy-staging.yml 
Enter fullscreen mode Exit fullscreen mode
  • job-deploy-kamal.yml is the shared job where Kamal runs
  • workflow-deploy-production.yml acts as a trigger for production
  • workflow-deploy-staging.yml acts as a trigger for staging

job-deploy-kamal.yml

name: Job - Deploy to Kamal on: workflow_call: inputs: kamal-destination: required: true type: string secrets: KAMAL_REGISTRY_USERNAME: required: true KAMAL_REGISTRY_PASSWORD: required: true SSH_PRIVATE_KEY: required: true DATABASE_URL: required: true QUEUE_DATABASE_URL: required: true SECRET_KEY_BASE: required: true env: DOCKER_BUILDKIT: 1 KAMAL_REGISTRY_USERNAME: ${{ secrets.KAMAL_REGISTRY_USERNAME }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} # App secrets DATABASE_URL: ${{ secrets.DATABASE_URL }} QUEUE_DATABASE_URL: ${{ secrets.QUEUE_DATABASE_URL }} SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} jobs: deploy: name: kamal deploy timeout-minutes: 10 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.KAMAL_REGISTRY_USERNAME }} password: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - run: gem install kamal - run: kamal lock release -d ${{ inputs.kamal-destination }} - run: kamal deploy -d ${{ inputs.kamal-destination }} 
Enter fullscreen mode Exit fullscreen mode

workflow-deploy-staging.yml

name: Workflow - Deployment (staging) on: push: branches: - main concurrency: group: deployment-staging cancel-in-progress: false jobs: deploy: uses: ./.github/workflows/job-deploy-kamal.yml with: kamal-destination: staging secrets: KAMAL_REGISTRY_USERNAME: ${{ secrets.KAMAL_REGISTRY_USERNAME }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} SSH_PRIVATE_KEY: ${{ secrets.STG_SSH_PRIVATE_KEY }} DATABASE_URL: ${{ secrets.STG_DATABASE_URL }} QUEUE_DATABASE_URL: ${{ secrets.STG_QUEUE_DATABASE_URL }} SECRET_KEY_BASE: ${{ secrets.STG_SECRET_KEY_BASE }} 
Enter fullscreen mode Exit fullscreen mode

workflow-deploy-production.yml

name: Workflow - Deployment (production) on: release: types: [published] concurrency: group: deployment-production cancel-in-progress: false jobs: deploy: uses: ./.github/workflows/job-deploy-kamal.yml with: kamal-destination: production secrets: KAMAL_REGISTRY_USERNAME: ${{ secrets.KAMAL_REGISTRY_USERNAME }} KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_PRIVATE_KEY }} DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }} QUEUE_DATABASE_URL: ${{ secrets.PROD_QUEUE_DATABASE_URL }} SECRET_KEY_BASE: ${{ secrets.PROD_SECRET_KEY_BASE }} 
Enter fullscreen mode Exit fullscreen mode

Kamal files

.kamal/ ├─ secrets-common config/ ├─ deploy.production.yml ├─ deploy.staging.yml ├─ deploy.yml 
Enter fullscreen mode Exit fullscreen mode

secrets-common

KAMAL_REGISTRY_USERNAME=$KAMAL_REGISTRY_USERNAME KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD DATABASE_URL=$DATABASE_URL QUEUE_DATABASE_URL=$QUEUE_DATABASE_URL SECRET_KEY_BASE=$SECRET_KEY_BASE 
Enter fullscreen mode Exit fullscreen mode

deploy.yml

service: myapp image: myorg/myapp registry: username: - KAMAL_REGISTRY_USERNAME password: - KAMAL_REGISTRY_PASSWORD proxy: ssl: true app_port: 3000 builder: arch: amd64 volumes: - "app_storage:/app/storage" 
Enter fullscreen mode Exit fullscreen mode

deploy.production.yml

servers: web: - 192.168.0.1 job: hosts: - 192.168.0.1 cmd: bin/jobs proxy: host: api.myapp.com env: clear: RAILS_LOG_TO_STDOUT: 1 WEB_CONCURRENCY: 2 JOB_CONCURRENCY: 1 SOLID_QUEUE_IN_PUMA: true secret: - DATABASE_URL - QUEUE_DATABASE_URL - SECRET_KEY_BASE 
Enter fullscreen mode Exit fullscreen mode

deploy.staging.yml

servers: web: - 192.168.0.2 job: hosts: - 192.168.0.2 cmd: bin/jobs proxy: host: api-staging.myapp.com env: clear: RAILS_LOG_TO_STDOUT: 1 WEB_CONCURRENCY: 0 JOB_CONCURRENCY: 1 SOLID_QUEUE_IN_PUMA: true secret: - DATABASE_URL - QUEUE_DATABASE_URL - SECRET_KEY_BASE 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)