Automating Releases with release-please
I was recently introduced to the release-please GitHub Action and have since implemented it in every one of my projects. It automates all of my CHANGELOG updates, version bumping, and release tagging. My delivery pipeline watches for new version tags, so triggering a full build and release only requires merging a PR.
With the basic setup (see below), the release-please workflow runs on every push to main. It identifies the new commits and updates your CHANGELOG and bumps the version number based on the conventional commit prefixes since the last release.
Conventional Commits
release-please uses the conventional commit prefixes in your git history to determine the next version.
| Prefix | Type | Version | New version |
|---|---|---|---|
fix: |
patch | 1.4.0 | 1.4.1 |
feat: |
minor | 1.4.0 | 1.5.0 |
feat!: (or BREAKING CHANGE in the footer) |
major | 1.4.0 | 2.0.0 |
chore, docs, refactor, test, perf, style |
none | 1.4.0 | N/A |
Read more about Conventional Commits.
Workflow
The basic workflow is a single action.
name: Release Please
on:
push:
branches: [main]
permissions:
# needed to commit the CHANGELOG and version bump
contents: write
# needed to create and maintain the release PR
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
release-type: dart # bumps the version in pubspec.yaml
The release-type tells release-please how your project is versioned — dart tracks the version in your pubspec.yaml. It also handles node, python, rust, simple (a plain version.txt), and more.
With this action in place, you'll see a release PR in your repo after your first conventional commit is pushed to main.
One caveat: your CI checks will not automatically run on this PR. This is because GitHub does not run CI for changes pushed with the secrets.GITHUB_TOKEN, which release-please uses by default. Your CI checks will sit, indefinitely, waiting for a status.
It's a deliberate choice by GitHub to prevent recursive workflow runs — a useful rule, but it slows us down here.
Getting CI to run on the release PR
The fix is to run release-please as a different identity. You've got two options: a Personal Access Token (PAT) or a GitHub App.
Why use a GitHub App?
GitHub's examples use a PAT because it's quick and easy, but it comes with a few drawbacks:
- Typical PATs grant access to every repo that user can access
- It's tied to you and your account. If you leave the org, disable your account, change your password, or reset your tokens, the PAT becomes invalid and the action will fail
- Fine-grained PATs expire (max 1 year) and classic PATs can expire, also leading to failure
- PATs share your rate limits
A GitHub App, on the other hand, is its own identity - the release PR shows up as authored by the bot. The token it creates is finely-tuned with specific permissions for one repo and it's short-lived (created when the job starts, expires when it ends). Because it's a separate identity, it also has its own rate limits.
Setup
A one-time, five-minute setup. Steps 1-4 happen in the GitHub App UI; step 5 is over in your repo.
- Create the App
- Open Settings > Developer settings > GitHub Apps and click New GitHub App
- Give your app a name (
my-project-release-please) - Provide a Homepage URL (it can be anything, like the repo URL)
- Uncheck Active in the Webhook section
- Grant permissions
- Contents: Read and write
- Pull requests: Read and write
- Issues: Read and write
- Generate a private key
- On the App's General page, scroll to the Private keys section near the bottom
- Click Generate a private key
- Download the key and keep it somewhere safe
- Install the App
- Click Install App in the sidebar
- Choose Only select repositories and select your target repo
- Add the repo secrets
- Open your repo settings (open the repository page and click the Settings tab)
- Click Secrets and Variables > Actions
- Create
RELEASE_PLEASE_APP_IDwith the App ID - Create
RELEASE_PLEASE_APP_PRIVATE_KEYwith the contents of the.pemfile you downloaded earlier
Finally, update your workflow to generate a temporary token using the create-github-app-token action:
name: Release Please
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
# NEW
# exchange the App ID and private key for a fresh token
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.RELEASE_PLEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }}
- uses: googleapis/release-please-action@v4
with:
release-type: dart
token: ${{ steps.app-token.outputs.token }}
The actions/create-github-app-token step exchanges your App ID and private key for a fresh installation token.
The payoff
That's the whole loop: commit with Conventional Commit prefixes, and release-please keeps a release PR up to date with your next version, an updated CHANGELOG, and — now that it runs as a GitHub App — a full set of passing CI checks.
No more hand-editing changelogs, guessing the next version number, or tagging releases from your terminal. Your commit messages already describe what changed; this just handles the bookkeeping.
Releasing is now a simple code review: read the PR, merge it, and ship.