Using Github Actions to Run Buildpacks
In my previous post, I showed you how to create an inline buildpack that live directly in your application repository. We created a Python buildpack that bundles uv for dependency installation, eliminating the need to maintain a separate buildpack repository.
But there’s one more piece to make this workflow truly powerful: continuous integration. Let’s set up Github Actions to automatically build and publish a Docker image every time you push code.
Setting Up the GitHub Action
Create a file at .github/workflows/build.yml in your repository with the following contents:
name: Build with Pack
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Pack CLI
uses: buildpacks/github-actions/setup-pack@v5.9.6
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Pack
run: pack build --publish ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest -t ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
Breaking Down the Workflow
Let’s walk through what each part does:
Triggers
on:
push:
branches:
- main
pull_request:
The workflow runs on every push to main and on pull requests. This means PRs get a test build, but only main branch pushes result in published images.
Permissions
permissions:
contents: read
packages: write
GitHub Actions needs explicit permission to write to GitHub Packages. The contents: read permission allows checking out your code, while packages: write enables pushing to ghcr.io.
Setup Steps
The workflow starts by checking out your code and installing the Pack CLI:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Pack CLI
uses: buildpacks/github-actions/setup-pack@v5.9.6
The Pack CLI is what actually executes the buildpack. The official GitHub Action makes installation simple.
Building and Publishing the Image
First, we authenticate to GitHub Container Registry using the built-in GITHUB_TOKEN:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Then run pack to build our application image:
- name: Build with Pack
run: pack build --publish ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest -t ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
This is the same command you’d run locally, but with the --publish flag. It builds your application using the inline buildpack in your repository with the Heroku builder, and then writes the resulting image directly to the container registry. We create two tags: latest for convenience and the commit SHA for precise version tracking.
Making Your Images Public
By default, packages on GitHub Container Registry are private. To make your image publicly accessible:
- Go to your repository on GitHub
- Click the “Packages” link on the right sidebar
- Click on your package name
- Click “Package settings” in the sidebar
- Scroll down to “Danger Zone” and click “Change visibility”
- Select “Public”
You can also link the package back to your repository from the package settings page.
Using Your Published Images
Once the workflow runs, you can pull your image from anywhere:
docker pull ghcr.io/yourusername/python-inline-buildpack:latest
Or use a specific version:
docker pull ghcr.io/yourusername/python-inline-buildpack:abc123def
This is perfect for deploying to Kubernetes, cloud platforms, or anywhere else that accepts container images.
If you see the following error, it means that you’re running on a different CPU architecture than Github:
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
ERROR: failed to launch: determine start command: when there is no default process a command is required
I’ll discuss how to solve this problem in a future post.
The Big Picture
With inline buildpacks and GitHub Actions, you have a complete CI/CD pipeline that:
- Keeps your buildpack logic with your application code
- Automatically builds on every commit
- Publishes versioned, reproducible container images
- Requires no separate infrastructure or buildpack repositories
You can iterate on your buildpack, test changes in pull requests, and automatically deploy when merging to main.
From here, you might want to:
- Set up deployment workflows that use your published images
- Create release tags that trigger special versioned builds
- Add build caching to speed up your CI runs
The inline buildpack pattern combined with GitHub Actions gives you a powerful foundation for building and shipping applications with custom build logic, all from a single repository.