Skip to content
Back to top

# CI/CD Integration

This guide shows how to integrate build into your continuous integration and deployment workflows.

## GitHub Actions

### Using hynek’s build-and-inspect-python-package

The recommended way to build and verify packages in GitHub Actions is using the hynek/build-and-inspect-python-package action. This action builds your package, inspects it for common issues, and provides the built artifacts.

    name: Build

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: hynek/build-and-inspect-python-package@v2

This action automatically:

- Builds both sdist and wheel

- Verifies the sdist contains all necessary files

- Checks for common packaging issues

- Uploads artifacts for download

- Works with any PEP 517 build backend

For more details, see the action documentation.

### Manual build with isolation

If you need more control, use build directly with isolated environments. `pipx` is pre-installed on all GitHub Actions runners:

    name: Build

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4

          - uses: actions/setup-python@v5
            with:
              python-version: "3.12"

          - name: Build package
            run: pipx run build

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

### Fast builds with pre-installed dependencies

For faster CI builds, pre-install build dependencies and use `--no-isolation`:

    name: Fast Build

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4

          - uses: actions/setup-python@v5
            with:
              python-version: "3.12"

          - name: Install dependencies
            run: |
              pip install build hatchling  # Replace hatchling with your backend

          - name: Build package
            run: python -m build --no-isolation

### Using uv for faster builds

Use uv as the installer for faster dependency installation:

    name: Build with uv

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4

          - uses: actions/setup-python@v5
            with:
              python-version: "3.12"

          - name: Install uv and build
            run: pip install uv build

          - name: Build package
            run: python -m build --installer=uv

### Matrix builds across Python versions

Test building with multiple Python versions:

    name: Build Matrix

    on: [push, pull_request]

    jobs:
      build:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

        steps:
          - uses: actions/checkout@v4

          - uses: actions/setup-python@v5
            with:
              python-version: ${{ matrix.python-version }}

          - name: Install build
            run: pip install build

          - name: Build package
            run: python -m build

### Building wheels for multiple platforms

For packages with C extensions or platform-specific code, use cibuildwheel:

    name: Build Wheels

    on: [push, pull_request]

    jobs:
      build_wheels:
        name: Build wheels on ${{ matrix.os }}
        runs-on: ${{ matrix.os }}
        strategy:
          matrix:
            os: [ubuntu-latest, windows-latest, macos-13, macos-14]

        steps:
          - uses: actions/checkout@v4

          - uses: pypa/cibuildwheel@v2.17

          - uses: actions/upload-artifact@v4
            with:
              name: wheels-${{ matrix.os }}
              path: ./wheelhouse/*.whl

      build_sdist:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: hynek/build-and-inspect-python-package@v2

See build vs Other Tools for when to use build vs cibuildwheel.

### Publishing to PyPI

Combine building and publishing in a release workflow using the PyPA publish action:

    name: Publish

    on:
      release:
        types: [published]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: hynek/build-and-inspect-python-package@v2

      publish:
        needs: build
        runs-on: ubuntu-latest
        permissions:
          id-token: write
        steps:
          - uses: actions/download-artifact@v4
            with:
              name: Packages
              path: dist

          - uses: pypa/gh-action-pypi-publish@release/v1

## GitLab CI

### Basic build

    build:
      image: python:3.12
      script:
        - pip install build
        - python -m build
      artifacts:
        paths:
          - dist/

### With caching

Cache pip packages to speed up builds:

    build:
      image: python:3.12
      cache:
        paths:
          - .cache/pip
      variables:
        PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
      script:
        - pip install build
        - python -m build
      artifacts:
        paths:
          - dist/

### Fast builds with pre-installed dependencies

    build:
      image: python:3.12
      script:
        - pip install build hatchling
        - python -m build --no-isolation
      artifacts:
        paths:
          - dist/

### Matrix builds

    .build_template:
      script:
        - pip install build
        - python -m build
      artifacts:
        paths:
          - dist/

    build:py310:
      extends: .build_template
      image: python:3.10

    build:py314:
      extends: .build_template
      image: python:3.14

## CircleCI

### Basic build

    version: 2.1

    jobs:
      build:
        docker:
          - image: cimg/python:3.12
        steps:
          - checkout
          - run:
              name: Install build
              command: pip install build
          - run:
              name: Build package
              command: python -m build
          - store_artifacts:
              path: dist/

    workflows:
      build_workflow:
        jobs:
          - build

## Travis CI

### Basic build

    language: python
    python:
      - "3.10"
      - "3.11"
      - "3.12"
      - "3.13"
      - "3.14"

    install:
      - pip install build

    script:
      - python -m build

## Azure Pipelines

### Basic build

    trigger:
      - main

    pool:
      vmImage: 'ubuntu-latest'

    strategy:
      matrix:
        Python310:
          python.version: '3.10'
        Python314:
          python.version: '3.14'

    steps:
    - task: UsePythonVersion@0
      inputs:
        versionSpec: '$(python.version)'

    - script: |
        pip install build
        python -m build
      displayName: 'Build package'

    - task: PublishBuildArtifacts@1
      inputs:
        pathToPublish: 'dist'
        artifactName: 'dist'

## Jenkins

### Declarative pipeline

    pipeline {
        agent any

        stages {
            stage('Build') {
                steps {
                    sh '''
                        python3 -m pip install build
                        python3 -m build
                    '''
                }
            }
            stage('Archive') {
                steps {
                    archiveArtifacts artifacts: 'dist/*', fingerprint: true
                }
            }
        }
    }

## Docker

### Multi-stage build

Build packages in a Docker container:

    FROM python:3.12-slim AS builder

    WORKDIR /app

    COPY pyproject.toml README.md ./
    COPY src/ src/

    RUN pip install build && python -m build

    # Runtime image
    FROM python:3.12-slim

    COPY --from=builder /app/dist/*.whl /tmp/
    RUN pip install /tmp/*.whl && rm /tmp/*.whl

    CMD ["python"]

## tox Integration

Using build within tox environments:

    [env_run_base]
    description = "run test suite"
    deps = ["pytest"]
    commands = [["pytest", "tests"]]

    [env.build]
    description = "build the package"
    deps = ["build"]
    commands = [["python", "-m", "build"]]

    [env."build-check"]
    description = "build and check the package"
    deps = [
        "build",
        "twine",
    ]
    commands = [
        ["python", "-m", "build"],
        ["twine", "check", "dist/*"],
    ]

Run the build environment:

    $ tox -e build

## Discovering Built Artifacts

Artifact filenames are dynamic, so pipelines that need to act on the exact files build produced (upload, sign, compute digests) should not guess or glob them. Pass `--report PATH` to capture a machine-readable list and read it back with a JSON tool such as `jq`. Pick a `PATH` outside the output directory so tools that upload `dist/*` wholesale do not trip over it:

    - name: Build and upload
      run: |
        python -m build --report build-report.json
        twine upload $(jq -r '.artifacts[].path' build-report.json)

The report also includes each artifact’s size and SHA-256 hash, useful for emitting provenance or checksums:

    $ jq -r '.artifacts[] | "\(.hashes.sha256)  \(.name)"' build-report.json

See Command-Line Interface for the full report schema.

## Verifying Builds

Always verify your built packages before publishing:

    - name: Check package
      run: |
        pip install twine
        twine check dist/*

Or use hynek’s action which includes verification automatically.

## Conditional Builds

Only build on specific events:

    name: Build

    on:
      push:
        branches: [main]
        tags: ['v*']
      pull_request:
        branches: [main]

    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: hynek/build-and-inspect-python-package@v2

      publish:
        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
        needs: build
        runs-on: ubuntu-latest
        permissions:
          id-token: write
        steps:
          - uses: actions/download-artifact@v4
          - uses: pypa/gh-action-pypi-publish@release/v1

This builds on every push and PR, but only publishes when pushing a version tag.

## Troubleshooting CI Builds

### Enable verbose output

Add `-vv` flag for debugging:

    - name: Build package
      run: python -m build -vv

### Preserve build artifacts

Upload the dist directory even on failure:

    - name: Upload artifacts
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: dist
        path: dist/

### Cache build environments

For repeated builds, cache pip’s download cache:

    - uses: actions/cache@v4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}

## See also

- Troubleshooting for build-specific issues

- Corporate Environments for private indexes and proxies

- build vs Other Tools for when to use build vs other tools

- hynek/build-and-inspect-python-package - Recommended GitHub Action

- cibuildwheel - For multi-platform wheel builds