PyO3/maturin-action

GitHub Action to install and run a custom maturin command with built-in support for cross compilation

TypeScript

190

722 commits

updated Oct 18, 2026

See the code

README

maturin-action

GitHub Actions

GitHub Action to install and run a custom maturin command with built-in support for cross compilation.

Usage

- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release

Enable a compiler cache with cache: sccache or cache: kache (or the equivalent sccache: true / kache: true booleans). Only one cache can be enabled. kache is installed from GitHub Releases and its store is persisted with GitHub Actions cache on the runner, including manylinux Docker builds.

To generate a GitHub Actions workflow for your project, try the maturin generate-ci github command.

mkdir -p .github/workflows
maturin generate-ci github > .github/workflows/CI.yml

Examples

If you want to build and publish a Python extension module for common Python versions, operating systems, and CPU architectures, take a look at the following examples:

Inputs

NameRequiredDescriptionTypeDefault
commandNomaturin command to runstringbuild
argsNoArguments to pass to maturin subcommandstring
maturin-versionNoThe version of maturin to use. Must match a tagged releasestringlatest
manylinuxNoControl the manylinux platform tag on linux, ignored on other platforms, use auto to build for lowest compatiblestringDefaults to auto for the publish command
targetNoThe --target option for Cargostring
containerNomanylinux docker container image namestringDefault depends on target and manylinux options, Set to off to disable manylinux docker build and build on the host instead.
docker-optionsNoAdditional Docker run options, for passing environment variables and etc.string
rust-toolchainNoRust toolchain name.stringDefaults to stable for Docker build. To use the latest available version for the host build, the user must specify this in the CI config or repo config.
rustup-componentsNoRustup componentsstringDefaults to empty
working-directoryNoThe working directory to run the command instringDefaults to the root of the repository
cacheNoCompiler cache: none, sccache, or kache. When unset, the sccache / kache booleans still applystring
sccacheNoEnable sccache for faster builds. Equivalent to cache: sccachebooleanDefaults to false
kacheNoEnable kache for faster builds. Equivalent to cache: kachebooleanDefaults to false
kache-versionNokache release to install when kache is enabled, like v0.19.0. Defaults to the latest GitHub releasestring
before-script-linuxNoScript to run before the maturin command on Linuxstring

manylinux Docker container

If a manylinux version is specified and the target is a Linux target (whether set explicitly via the target input or implied from the host), this action will run the maturin command in a manylinux docker container.

For native host builds the pypa manylinux docker images are used. For cross compilation, images from rust-cross are typically used by default. This can be overridden by specifying the container input.

Here is a table detailing examples of the default selected containers:

Target architectureRunner architecturemanylinux versionDefault containerRequires QEMU
x86_64x86_642014/2_17quay.io/pypa/manylinux2014_x86_64:latestNo
x86_64x86_642_28quay.io/pypa/manylinux_2_28_x86_64:latestNo
x86_64x86_642_34quay.io/pypa/manylinux_2_34_x86_64:latestNo
x86_64aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:x86_64No
x86_64aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:x86_64No
i686x86_642014/2_17quay.io/pypa/manylinux2014_i686:latestNo
i686x86_642_28quay.io/pypa/manylinux_2_28_i686:latestNo
i686x86_642_34quay.io/pypa/manylinux_2_34_i686:latestNo
i686aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:i686No
aarch64x86_642014/2_17ghcr.io/rust-cross/manylinux2014-cross:aarch64No
aarch64x86_642_28ghcr.io/rust-cross/manylinux_2_28-cross:aarch64No
aarch64aarch642014/2_17quay.io/pypa/manylinux2014_aarch64:latestNo
aarch64aarch642_28quay.io/pypa/manylinux_2_28_aarch64:latestNo
aarch64aarch642_34quay.io/pypa/manylinux_2_34_aarch64:latestNo
armv7lx86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:armv7No
armv7lx86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:armv7No
ppc64lex86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:ppc64leNo
ppc64lex86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:ppc64leNo
ppc64x86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:ppc64No
s390xx86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:s390xNo
s390xx86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:s390xNo
riscv64x86_64, aarch642_31ghcr.io/rust-cross/manylinux_2_31-cross:riscv64No
riscv64x86_64, aarch642_39quay.io/pypa/manylinux_2_39_riscv64:latestYes
riscv64riscv642_39quay.io/pypa/manylinux_2_39_riscv64:latestNo
loongarch64x86_64, aarch642_36ghcr.io/rust-cross/manylinux_2_36-cross:loongarch64No

You can override it by supplying the container input.

If you use wish to use an official manylinux image for a different architecture from the runner, you will need to set up QEMU before using this action, for example

- name: Setup QEMU
  uses: docker/setup-qemu-action@v3
- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release

Note that the actions/setup-python action won't affect manylinux build since it's containerized, so if you want to build for certain Python version for Linux, use -i pythonX.Y in the args option in PyO3/maturin-action instead, for example

- uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.10

To build for every available interpreter at once — including the free-threaded builds — use --find-interpreter; see Free-threaded CPython below.

Free-threaded CPython

maturin builds wheels for the free-threaded ("no-GIL") CPython builds automatically when you pass --find-interpreter and a free-threaded interpreter is available. Free-threaded interpreters carry a t suffix (python3.14t, python3.15t, …); maturin discovers the officially supported ones (CPython 3.14 and newer) the same way it discovers the regular builds — the experimental 3.13t is not discovered automatically. Discovery needs a reasonably recent maturin, which the action installs by default.

Linux (manylinux)

No configuration needed — the default manylinux containers ship the free-threaded interpreters, and the action puts every interpreter under /opt/python on PATH, so --find-interpreter finds them:

- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release --find-interpreter

macOS, Windows, and non-manylinux Linux (manylinux: off)

These run on the host, so the interpreters come from your own actions/setup-python step. Install the free-threaded build alongside the regular one:

- uses: actions/setup-python@v6
  with:
    python-version: |
      3.14
      3.14t
- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release --find-interpreter

setup-python exposes the free-threaded build under its t-suffixed name (python3.14t, or python3.14t.exe on Windows), which is what --find-interpreter looks for.

Windows: build the regular and free-threaded interpreters in separate jobs

On Windows, co-installing the regular and free-threaded interpreters of the same minor version in one setup-python step can fail (python/cpython#127294, #313). Use a matrix with one interpreter per job instead.

Stable ABI (abi3 / abi3t)

The free-threaded build has its own stable ABI, abi3t (PEP 803, added in CPython 3.15), distinct from the GIL-enabled abi3. PyO3 exposes both as Cargo features, and projects can enable both when they want stable ABI wheels by default:

pyo3 = { version = "0.29", features = ["abi3-py310", "abi3t-py315"] }

One maturin invocation selects at most one stable ABI family, so do not expect one --find-interpreter build to emit both forward-compatible wheels. To publish a complete wheel set for current non-EOL CPython releases, run separate maturin builds with different interpreters. The same default Cargo features can be used for each build:

- name: Build abi3 wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.10

- name: Build CPython 3.14t wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.14t

- name: Build abi3t wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.15t

The abi3-py310 wheel supports GIL-enabled CPython 3.10 and newer. The abi3t-py315 wheel supports CPython 3.15 and newer, both GIL-enabled and free-threaded. Free-threaded CPython 3.14 predates abi3t, so the python3.14t build produces the version-specific cp314-cp314t wheel.

If stable ABI support is behind a project feature, pass that feature to both builds. If a project enables only abi3 (no abi3t), --find-interpreter builds no free-threaded stable ABI wheel; request a version-specific free-threaded wheel explicitly with, e.g., -i python3.14t.

Hardening Release pipelines

We recommend the following steps for hardening release pipelines:

  • When targeting PyPI, set --compatibility pypi to activate its pre-upload check
  • Set an explicit manylinux: version for each target to prevent silent regressions
  • Pin both maturin-action and maturin version, and use a service such as renovate to update them
strategy:
  matrix:
    platform:
      - target: aarch64-unknown-linux-gnu
        arch: aarch64
        manylinux: 2_28
      - target: armv7-unknown-linux-gnueabihf
        arch: armv7
        manylinux: 2_17

steps:
  # [...]
  - name: "Build wheels"
    uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
    with:
      maturin-version: v1.11.5
      target: ${{ matrix.platform.target }}
      manylinux: ${{ matrix.platform.manylinux }}
      args: --release --locked --compatibility pypi

An example renovate configuration

// Maturin version used in maturin-action
{
  customType: "regex",
  managerFilePatterns: ["/.github/workflows/.*\\.yml$/"],
  matchStrings: ["maturin-version: (?<currentValue>v\\d+\\.\\d+\\.\\d+)"],
  depNameTemplate: "maturin",
  packageNameTemplate: "PyO3/maturin",
  datasourceTemplate: "github-releases",
},

Contributing

To build after code changes:

npm run all

License

This work is released under the MIT license. A copy of the license is provided in the LICENSE file.

cffi
github-actions
hacktoberfest
manylinux
maturin
pyo3
rust-cpython
uniffi

PyO3/maturin-action

GitHub Action to install and run a custom maturin command with built-in support for cross compilation

TypeScript

190

722 commits

updated Oct 18, 2026

See the code

README

maturin-action

GitHub Actions

GitHub Action to install and run a custom maturin command with built-in support for cross compilation.

Usage

- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release

Enable a compiler cache with cache: sccache or cache: kache (or the equivalent sccache: true / kache: true booleans). Only one cache can be enabled. kache is installed from GitHub Releases and its store is persisted with GitHub Actions cache on the runner, including manylinux Docker builds.

To generate a GitHub Actions workflow for your project, try the maturin generate-ci github command.

mkdir -p .github/workflows
maturin generate-ci github > .github/workflows/CI.yml

Examples

If you want to build and publish a Python extension module for common Python versions, operating systems, and CPU architectures, take a look at the following examples:

Inputs

NameRequiredDescriptionTypeDefault
commandNomaturin command to runstringbuild
argsNoArguments to pass to maturin subcommandstring
maturin-versionNoThe version of maturin to use. Must match a tagged releasestringlatest
manylinuxNoControl the manylinux platform tag on linux, ignored on other platforms, use auto to build for lowest compatiblestringDefaults to auto for the publish command
targetNoThe --target option for Cargostring
containerNomanylinux docker container image namestringDefault depends on target and manylinux options, Set to off to disable manylinux docker build and build on the host instead.
docker-optionsNoAdditional Docker run options, for passing environment variables and etc.string
rust-toolchainNoRust toolchain name.stringDefaults to stable for Docker build. To use the latest available version for the host build, the user must specify this in the CI config or repo config.
rustup-componentsNoRustup componentsstringDefaults to empty
working-directoryNoThe working directory to run the command instringDefaults to the root of the repository
cacheNoCompiler cache: none, sccache, or kache. When unset, the sccache / kache booleans still applystring
sccacheNoEnable sccache for faster builds. Equivalent to cache: sccachebooleanDefaults to false
kacheNoEnable kache for faster builds. Equivalent to cache: kachebooleanDefaults to false
kache-versionNokache release to install when kache is enabled, like v0.19.0. Defaults to the latest GitHub releasestring
before-script-linuxNoScript to run before the maturin command on Linuxstring

manylinux Docker container

If a manylinux version is specified and the target is a Linux target (whether set explicitly via the target input or implied from the host), this action will run the maturin command in a manylinux docker container.

For native host builds the pypa manylinux docker images are used. For cross compilation, images from rust-cross are typically used by default. This can be overridden by specifying the container input.

Here is a table detailing examples of the default selected containers:

Target architectureRunner architecturemanylinux versionDefault containerRequires QEMU
x86_64x86_642014/2_17quay.io/pypa/manylinux2014_x86_64:latestNo
x86_64x86_642_28quay.io/pypa/manylinux_2_28_x86_64:latestNo
x86_64x86_642_34quay.io/pypa/manylinux_2_34_x86_64:latestNo
x86_64aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:x86_64No
x86_64aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:x86_64No
i686x86_642014/2_17quay.io/pypa/manylinux2014_i686:latestNo
i686x86_642_28quay.io/pypa/manylinux_2_28_i686:latestNo
i686x86_642_34quay.io/pypa/manylinux_2_34_i686:latestNo
i686aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:i686No
aarch64x86_642014/2_17ghcr.io/rust-cross/manylinux2014-cross:aarch64No
aarch64x86_642_28ghcr.io/rust-cross/manylinux_2_28-cross:aarch64No
aarch64aarch642014/2_17quay.io/pypa/manylinux2014_aarch64:latestNo
aarch64aarch642_28quay.io/pypa/manylinux_2_28_aarch64:latestNo
aarch64aarch642_34quay.io/pypa/manylinux_2_34_aarch64:latestNo
armv7lx86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:armv7No
armv7lx86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:armv7No
ppc64lex86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:ppc64leNo
ppc64lex86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:ppc64leNo
ppc64x86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:ppc64No
s390xx86_64, aarch642014/2_17ghcr.io/rust-cross/manylinux2014-cross:s390xNo
s390xx86_64, aarch642_28ghcr.io/rust-cross/manylinux_2_28-cross:s390xNo
riscv64x86_64, aarch642_31ghcr.io/rust-cross/manylinux_2_31-cross:riscv64No
riscv64x86_64, aarch642_39quay.io/pypa/manylinux_2_39_riscv64:latestYes
riscv64riscv642_39quay.io/pypa/manylinux_2_39_riscv64:latestNo
loongarch64x86_64, aarch642_36ghcr.io/rust-cross/manylinux_2_36-cross:loongarch64No

You can override it by supplying the container input.

If you use wish to use an official manylinux image for a different architecture from the runner, you will need to set up QEMU before using this action, for example

- name: Setup QEMU
  uses: docker/setup-qemu-action@v3
- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release

Note that the actions/setup-python action won't affect manylinux build since it's containerized, so if you want to build for certain Python version for Linux, use -i pythonX.Y in the args option in PyO3/maturin-action instead, for example

- uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.10

To build for every available interpreter at once — including the free-threaded builds — use --find-interpreter; see Free-threaded CPython below.

Free-threaded CPython

maturin builds wheels for the free-threaded ("no-GIL") CPython builds automatically when you pass --find-interpreter and a free-threaded interpreter is available. Free-threaded interpreters carry a t suffix (python3.14t, python3.15t, …); maturin discovers the officially supported ones (CPython 3.14 and newer) the same way it discovers the regular builds — the experimental 3.13t is not discovered automatically. Discovery needs a reasonably recent maturin, which the action installs by default.

Linux (manylinux)

No configuration needed — the default manylinux containers ship the free-threaded interpreters, and the action puts every interpreter under /opt/python on PATH, so --find-interpreter finds them:

- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release --find-interpreter

macOS, Windows, and non-manylinux Linux (manylinux: off)

These run on the host, so the interpreters come from your own actions/setup-python step. Install the free-threaded build alongside the regular one:

- uses: actions/setup-python@v6
  with:
    python-version: |
      3.14
      3.14t
- uses: PyO3/maturin-action@v1
  with:
    command: build
    args: --release --find-interpreter

setup-python exposes the free-threaded build under its t-suffixed name (python3.14t, or python3.14t.exe on Windows), which is what --find-interpreter looks for.

Windows: build the regular and free-threaded interpreters in separate jobs

On Windows, co-installing the regular and free-threaded interpreters of the same minor version in one setup-python step can fail (python/cpython#127294, #313). Use a matrix with one interpreter per job instead.

Stable ABI (abi3 / abi3t)

The free-threaded build has its own stable ABI, abi3t (PEP 803, added in CPython 3.15), distinct from the GIL-enabled abi3. PyO3 exposes both as Cargo features, and projects can enable both when they want stable ABI wheels by default:

pyo3 = { version = "0.29", features = ["abi3-py310", "abi3t-py315"] }

One maturin invocation selects at most one stable ABI family, so do not expect one --find-interpreter build to emit both forward-compatible wheels. To publish a complete wheel set for current non-EOL CPython releases, run separate maturin builds with different interpreters. The same default Cargo features can be used for each build:

- name: Build abi3 wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.10

- name: Build CPython 3.14t wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.14t

- name: Build abi3t wheel
  uses: PyO3/maturin-action@v1
  with:
    args: --release -i python3.15t

The abi3-py310 wheel supports GIL-enabled CPython 3.10 and newer. The abi3t-py315 wheel supports CPython 3.15 and newer, both GIL-enabled and free-threaded. Free-threaded CPython 3.14 predates abi3t, so the python3.14t build produces the version-specific cp314-cp314t wheel.

If stable ABI support is behind a project feature, pass that feature to both builds. If a project enables only abi3 (no abi3t), --find-interpreter builds no free-threaded stable ABI wheel; request a version-specific free-threaded wheel explicitly with, e.g., -i python3.14t.

Hardening Release pipelines

We recommend the following steps for hardening release pipelines:

  • When targeting PyPI, set --compatibility pypi to activate its pre-upload check
  • Set an explicit manylinux: version for each target to prevent silent regressions
  • Pin both maturin-action and maturin version, and use a service such as renovate to update them
strategy:
  matrix:
    platform:
      - target: aarch64-unknown-linux-gnu
        arch: aarch64
        manylinux: 2_28
      - target: armv7-unknown-linux-gnueabihf
        arch: armv7
        manylinux: 2_17

steps:
  # [...]
  - name: "Build wheels"
    uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
    with:
      maturin-version: v1.11.5
      target: ${{ matrix.platform.target }}
      manylinux: ${{ matrix.platform.manylinux }}
      args: --release --locked --compatibility pypi

An example renovate configuration

// Maturin version used in maturin-action
{
  customType: "regex",
  managerFilePatterns: ["/.github/workflows/.*\\.yml$/"],
  matchStrings: ["maturin-version: (?<currentValue>v\\d+\\.\\d+\\.\\d+)"],
  depNameTemplate: "maturin",
  packageNameTemplate: "PyO3/maturin",
  datasourceTemplate: "github-releases",
},

Contributing

To build after code changes:

npm run all

License

This work is released under the MIT license. A copy of the license is provided in the LICENSE file.

cffi
github-actions
hacktoberfest
manylinux
maturin
pyo3
rust-cpython
uniffi

Project health

Overall

8.4/10

Maintained

9/10

Code review

10/10
OpenSSF Scorecard

Significant stargazers

Álvaro Mondéjar Rubio

142 followers · starred Feb 2023

Emma Smith

477 followers · starred Feb 2026

Jon Mease

272 followers · starred Apr 2023

c-bata

742 followers · starred Aug 2026