|

GitHub Actions CI/CD (9/10): Release Workflow — Tag, Build, Publish

← Previous GitHub Actions CI/CD (9/10) Next →

Summary: Create a release workflow that triggers when you push a version tag. It builds your package, creates a GitHub Release, and publishes to PyPI using Trusted Publishing — no API tokens stored as secrets.

KeyValue
Package namehelloci
Working directory~/projects/helloci
Release workflow.github/workflows/release.yml
Triggertag push matching v*
Build toolpython -m build
PyPI publishingTrusted Publishing (OIDC)

0. Prerequisites

  • The helloci project with CI, branch protection, and artifacts from Parts 1–8
  • A PyPI account at pypi.org
  • The gh CLI installed (for creating releases from the command line)

1. CI vs CD

Up to now, everything you have built is Continuous Integration (CI) — automated checks that run on every push and pull request. CI answers: “is the code correct?”

Continuous Deployment (CD) goes further — when certain conditions are met (like a version tag), the pipeline automatically builds and ships the software. CD answers: “is the code delivered?”

StageTriggerWhat happens
CIEvery push / PRLint, test, report
CDVersion tag (v1.0.0)Build, release, publish

This tutorial adds the CD half.


2. Add the Build Tool

Add build to your development dependencies in pyproject.toml.

[project.optional-dependencies]
db = [
    "psycopg[binary]>=3.1",
]
test = [
    "pytest>=8.0",
    "ruff>=0.4",
    "psycopg[binary]>=3.1",
]
build = [
    "build>=1.0",
]Code language: TOML, also INI (ini)

Install it.

pip install -e ".[build]"Code language: Shell Session (shell)

3. Build the Package Locally

python -m buildCode language: Shell Session (shell)
Successfully built helloci-0.1.0.tar.gz and helloci-0.1.0-py3-none-any.whl

This creates two files in the dist/ directory.

FileWhat it is
helloci-0.1.0.tar.gzSource distribution (sdist) — the raw source code
helloci-0.1.0-py3-none-any.whlWheel — the pre-built, installable package

PyPI accepts both. The wheel is what pip install downloads when available because it installs faster.

Tip: Add dist/ to your .gitignore if it is not already there.


4. Configure Trusted Publishing on PyPI

Trusted Publishing uses OpenID Connect (OIDC) to let GitHub Actions publish to PyPI without storing an API token. PyPI trusts GitHub as an identity provider.

Log in to pypi.org and navigate to Your projects > helloci > Settings > Publishing (or Manage > Settings > Publishing if the project exists).

If the project does not exist yet on PyPI, go to Publishing under your account settings and add a pending publisher.

Fill in the form:

FieldValue
PyPI project namehelloci
Owner<YOUR_GITHUB_USERNAME>
Repositoryhelloci
Workflow namerelease.yml
Environment namepypi

Click Add.

This tells PyPI: “trust tokens issued by GitHub Actions from this specific repository and workflow.”

Note: Trusted Publishing is the recommended way to publish to PyPI. It replaces the older method of storing a PyPI API token as a GitHub secret. No credentials are stored anywhere.


5. Create the Release Workflow

Create .github/workflows/release.yml.

name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install build tool
        run: pip install build

      - name: Build package
        run: python -m build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  github-release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
        run: >
          gh release create ${{ github.ref_name }}
          dist/*
          --title "${{ github.ref_name }}"
          --generate-notes

  publish-pypi:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1Code language: YAML (yaml)

This workflow has three jobs that run in sequence.


6. Understand the Release Workflow

Trigger:

on:
  push:
    tags:
      - "v*"Code language: YAML (yaml)

The workflow only runs when you push a tag that starts with v — like v1.0.0, v0.2.0, or v1.0.0-rc1.

Job 1 — build:

Builds the wheel and sdist, then uploads them as an artifact so the next jobs can use them.

Job 2 — github-release:

    needs: build
    permissions:
      contents: writeCode language: YAML (yaml)
  • needs: build — waits for the build job to finish
  • permissions: contents: write — grants the workflow token permission to create releases

The gh release create command creates a GitHub Release with auto-generated release notes and attaches the wheel and sdist files.

Job 3 — publish-pypi:

    environment: pypi
    permissions:
      id-token: writeCode language: YAML (yaml)
  • environment: pypi — must match the environment name you configured on PyPI
  • permissions: id-token: write — allows the workflow to request an OIDC token
  • pypa/gh-action-pypi-publish@release/v1 — the official PyPI publish action

Note: The id-token: write permission is what makes Trusted Publishing work. GitHub issues a short-lived OIDC token that PyPI verifies against your Trusted Publishing configuration. No API key is needed.


7. Create the GitHub Environment

Before the workflow can use the pypi environment, you need to create it.

Navigate to Settings > Environments in your repository. Click New environment. Name it pypi. Click Configure environment.

Optionally, add a protection rule:

  • Required reviewers — require someone to approve before the publish job runs

This adds a manual approval step to your release pipeline. The build and GitHub Release happen automatically, but publishing to PyPI waits for a human to click “approve.”

Tip: Required reviewers are optional but recommended for public packages. They prevent accidental publishes from typo tags.


8. Tag and Release

Make sure all your changes are committed and pushed to main.

git add .
git commit -m "Add release workflow with Trusted Publishing"
git pushCode language: Shell Session (shell)

Create and push a version tag.

git tag v0.1.0
git push origin v0.1.0Code language: Shell Session (shell)

Open the Actions tab. You see the Release workflow running. It progresses through three jobs.

build ✓
github-release ✓
publish-pypi ✓Code language: Shell Session (shell)

Check your GitHub Releases page — you see a v0.1.0 release with the wheel and sdist attached.

Check PyPI — your package is live at https://pypi.org/project/helloci/.

Warning: Once a version is published to PyPI, it cannot be overwritten. If you need to fix something, bump the version number and tag again.


9. Update the Version for Future Releases

When you are ready for the next release, update the version in pyproject.toml.

version = "0.2.0"Code language: TOML, also INI (ini)

Commit, tag, and push.

git add pyproject.toml
git commit -m "Bump version to 0.2.0"
git push
git tag v0.2.0
git push origin v0.2.0Code language: Shell Session (shell)

The release workflow runs again automatically.


Summary

You created a release workflow that automates the entire publish process: build the package, create a GitHub Release, and publish to PyPI.

  • Tag triggerv* tags start the release pipeline
  • python -m build — produces a wheel and sdist
  • gh release create — creates a GitHub Release with auto-generated notes
  • Trusted Publishing — PyPI trusts GitHub’s OIDC tokens, no API key needed
  • permissions: id-token: write — enables OIDC token generation
  • environment: pypi — ties the workflow to your PyPI Trusted Publishing config

You now have CI (lint, test on every PR) and CD (build and publish on every tag). In Part 10 you will optimize the full pipeline — split jobs, add caching, and schedule nightly runs.

Similar Posts

Leave a Reply