Build a custom cx binary#

This guide shows how to build a cx binary with your own set of conda packages baked in. This is useful when you want to distribute a bootstrapper that includes domain-specific packages (e.g. numpy, pandas) out of the box.

Using the GitHub Action#

The simplest approach. Add a workflow to your repo that calls the cx composite action:

name: Build custom cx
on: [push]

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: jezdez/conda-express@main
        id: cx
        with:
          packages: "python >=3.12, conda >=25.1, conda-rattler-solver, conda-spawn, numpy, pandas"

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.cx.outputs.asset-name }}
          path: ${{ steps.cx.outputs.binary-path }}

The action builds cx for the runner’s platform and outputs the path to the binary. Use a matrix to build for multiple platforms.

See the GitHub Action reference for all inputs and outputs.

Using the reusable workflow#

If you want all 5 platforms built without managing a matrix yourself:

name: Build custom cx
on: [push]

jobs:
  build-cx:
    uses: jezdez/conda-express/.github/workflows/build.yml@main
    with:
      packages: "python >=3.12, conda >=25.1, conda-rattler-solver, conda-spawn, numpy, pandas"
      channels: "conda-forge"
      exclude: "conda-libmamba-solver"

Binary artifacts for all platforms are uploaded automatically.

Building locally#

Set environment variables to override the default package list, then build:

git clone https://github.com/jezdez/conda-express.git
cd conda-express

CX_PACKAGES="python >=3.12, conda >=25.1, conda-rattler-solver, conda-spawn, numpy" \
  pixi run build

The binary is at target/release/cx.

Available overrides#

Variable

Effect

CX_PACKAGES

Replace the default package list

CX_CHANNELS

Replace the default channels

CX_EXCLUDE

Replace the default exclusions

Empty values are ignored. See the configuration reference for details on how overrides interact with the lockfile cache.

Choosing packages#

When specifying packages, keep in mind:

  • Always include the core set: python, conda, conda-rattler-solver, and conda-spawn (cx depends on these at runtime)

  • Use MatchSpec syntax for version constraints (e.g. numpy >=1.26)

  • The build performs a full dependency solve at compile time, so all transitive dependencies are resolved and locked

  • The resulting binary is self-contained with an embedded lockfile