Eduardo Lomeli
Posted on November 10, 2024
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_
andPROD_
. - Make sure you have created
SSH_PRIVATE_KEY
,KAMAL_REGISTRY_USERNAME
andKAMAL_REGISTRY_PASSWORD
secrets.
GitHub Action files
.github/
├─ workflows/
├─ job-deploy-kamal.yml
├─ workflow-deploy-production.yml
├─ workflow-deploy-staging.yml
-
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 }}
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 }}
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 }}
Kamal files
.kamal/
├─ secrets-common
config/
├─ deploy.production.yml
├─ deploy.staging.yml
├─ deploy.yml
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
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"
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
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
💖 💪 🙅 🚩
Eduardo Lomeli
Posted on November 10, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
kamal Kamal: Speed up the image builds using managed third-party builders and GitHub Actions
November 10, 2024