Deployment

Deployment with GitHub Actions

Trigger Kamal deploys from GitHub Actions.

Workflow file

This repo uses:

  • .github/workflows/b.yaml

Trigger

  • Push to master
  • GitHub Environment: production

Required environment secrets

Set these in Settings -> Environments -> production -> Secrets:

  • PROD_SSH_PRIVATE_KEY
  • POSTGRES_PASSWORD
  • DATABASE_URL
  • S3_ACCESS_KEY_ID
  • S3_SECRET_ACCESS_KEY

The workflow builds .kamal/secrets from these values before deploy.

What the workflow does

  1. Checkout repository.
  2. Load SSH private key with webfactory/ssh-agent.
  3. Install Ruby and Kamal.
  4. Create .kamal/secrets.
  5. Run kamal deploy.

Minimal secrets block generated in CI

POSTGRES_PASSWORD=...
DATABASE_URL=...
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...

Example workflow (.github/workflows/b.yaml)

name: Deploy

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    env:
      DOCKER_BUILDKIT: 1

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY || secrets.SSH_PRIVATE_KEY }}

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: "4.0.1"
          bundler-cache: true

      - name: Install Kamal
        run: gem install kamal -N

      - name: Prepare Kamal secrets
        working-directory: ./example_app
        run: |
          mkdir -p .kamal
          cat > .kamal/secrets <<EOF
          POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
          DATABASE_URL=${{ secrets.DATABASE_URL }}
          S3_ACCESS_KEY_ID=${{ secrets.S3_ACCESS_KEY_ID }}
          S3_SECRET_ACCESS_KEY=${{ secrets.S3_SECRET_ACCESS_KEY }}
          EOF

      - name: Deploy command
        working-directory: ./example_app
        run: kamal deploy

Verify

  • Push a small change to master.
  • Confirm workflow passes.
  • Confirm https://app.example.com/healthz returns 200.

In this example, working-directory: ./example_app is used because the app lives in a subdirectory. If your app is at repository root, remove working-directory from those steps.

On this page