Skip to content

Instantly share code, notes, and snippets.

@marckohlbrugge
Created January 9, 2026 07:58
Show Gist options
  • Select an option

  • Save marckohlbrugge/6d4aadf5a56474e3cc39881c3971b7ca to your computer and use it in GitHub Desktop.

Select an option

Save marckohlbrugge/6d4aadf5a56474e3cc39881c3971b7ca to your computer and use it in GitHub Desktop.
Optimistic Builds™ with Kamal
# Optimistic Builds for Kamal
#
# This workflow speeds up deploys by building the Docker image in parallel with CI.
# Instead of: CI → Build → Deploy (sequential)
# We do: CI ↘
# → Deploy
# Build ↗
#
# The "optimistic" part: we start building before knowing if tests pass.
# Since CI usually passes, this saves ~2 minutes per deploy.
name: Deploy
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
# Your normal CI setup here
steps:
- uses: actions/checkout@v4
# ... your test steps
# Build Docker image in parallel with tests (optimistic build)
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up SSH for Kamal
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
# Build and push image to your registry
- name: Build and push image
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
VERSION: ${{ github.sha }}
run: bundle exec kamal build push --version=$VERSION
# Pre-pull image on the server so deploy is instant
- name: Pre-pull image on server
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
VERSION: ${{ github.sha }}
run: bundle exec kamal build pull --version=$VERSION
# Deploy only runs after BOTH test and build succeed
deploy:
needs: [test, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up SSH for Kamal
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
# Deploy the pre-built, pre-pulled image
# --skip-push: Don't build/push, image already exists
# --version: Use the specific image we built earlier
- name: Deploy
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
VERSION: ${{ github.sha }}
run: bundle exec kamal deploy --skip-push --version=$VERSION
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment