conda-express (cx)
A lightweight, single-binary bootstrapper for conda, powered by rattler. The cx binary is short for conda express.
cx replaces the miniconda/constructor installation pattern with a ~17 MB static binary that bootstraps a fully functional conda environment in seconds.
Quick start
# Bootstrap a conda installation (first run only, ~3–5 s)
# Use conda normally — cx delegates transparently
# Activate environments using conda-spawn (no shell init needed)
On first use, cx automatically installs conda and its plugins into ~/.cx from an embedded lockfile. Subsequent invocations hand off directly to the installed conda binary with no overhead.
What gets installed
cx installs a minimal conda stack from conda-forge:
| Package | Role |
|---|---|
| python >= 3.12 | Runtime |
| conda >= 25.1 | Package manager |
| conda-rattler-solver | Rust-based solver (replaces libmamba) |
| conda-spawn | Subprocess-based environment activation |
| conda-pypi | PyPI interoperability |
| conda-self | Base environment self-management |
The conda-libmamba-solver and its 27 exclusive native dependencies (libsolv, libarchive, libcurl, spdlog, etc.) are excluded by default, reducing the install from 113 to 86 packages.
Installation
Homebrew (recommended)
The easiest way to install on macOS and Linux:
Update later with brew upgrade cx.
Shell script
macOS / Linux:
|
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://jezdez.github.io/conda-express/get-cx.ps1 | iex"
The script detects your platform, downloads the right binary, verifies the checksum, updates your shell profile / PATH, and runs cx bootstrap. Customize with environment variables:
CX_INSTALL_DIR— where to place the binary (default:~/.local/binor%USERPROFILE%\.local\bin)CX_VERSION— specific version to install (default:latest)CX_NO_PATH_UPDATE— set to skip shell profile / PATH modificationCX_NO_BOOTSTRAP— set to skip runningcx bootstrap
From GitHub Releases
Download the binary for your platform from the latest release:
| Platform | File |
|---|---|
| Linux x86_64 | cx-x86_64-unknown-linux-gnu |
| Linux ARM64 | cx-aarch64-unknown-linux-gnu |
| macOS x86_64 (Intel) | cx-x86_64-apple-darwin |
| macOS ARM64 (Apple Silicon) | cx-aarch64-apple-darwin |
| Windows x86_64 | cx-x86_64-pc-windows-msvc.exe |
Each file has a matching .sha256 checksum.
Docker
A minimal, hardened multi-arch image (~37 MB) is published to GHCR on every release:
The image runs as non-root (uid 65532), works with --read-only, and includes provenance attestations and SBOMs. Docker Desktop on macOS and Windows automatically selects the right architecture (linux/amd64 or linux/arm64).
# Run conda commands through cx
Use as a container in CI (GitHub Actions):
jobs:
test:
container: ghcr.io/jezdez/conda-express:latest
steps:
- run: cx bootstrap && cx create -n test python numpy
Use as a base for multi-stage application builds:
FROM ghcr.io/jezdez/conda-express:latest AS conda-builder
RUN cx bootstrap && cx create -n app python numpy pandas
FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=conda-builder /home/nonroot/.cx/envs/app /opt/conda
From PyPI
From crates.io
The package is published as conda-express on PyPI and crates.io.
Building from source
Requires pixi (recommended) or Rust (edition 2024).
With pixi (recommended)
pixi manages the Rust toolchain from conda-forge for reproducible builds:
With system Rust
# Build (first build solves packages at compile time — needs network)
# Binary is at target/release/cx
The first build runs a compile-time solve via build.rs, generating a rattler-lock v6 lockfile that gets embedded into the binary. Subsequent builds reuse the cached lockfile unless pixi.toml changes.
Configuration
Package specs, channels, and exclusions live in the [tool.cx] section of pixi.toml:
[]
= ["conda-forge"]
= [
"python >=3.12",
"conda >=25.1",
"conda-rattler-solver",
"conda-spawn",
"conda-pypi",
"conda-self",
]
= ["conda-libmamba-solver"]
Edit this section to customize what cx installs, then rebuild. You can also override these values at build time using environment variables — see Building custom cx binaries below.
CLI reference
cx bootstrap [OPTIONS] Bootstrap a fresh conda installation
--force Re-bootstrap even if prefix exists
--prefix DIR Target directory (default: ~/.cx)
--channel CH Channels (default: conda-forge)
--package PKG Additional packages to install
--exclude PKG Packages to exclude (default: conda-libmamba-solver)
--no-exclude Disable default exclusions
--no-lock Ignore embedded lockfile, do a live solve
--lockfile PATH Use an external lockfile instead
cx status [--prefix DIR] Show cx installation status
cx shell [ENV] Alias for conda spawn (activate via subshell)
cx uninstall [OPTIONS] Remove cx, conda prefix, and all environments
--prefix DIR Target directory (default: ~/.cx)
-y, --yes Skip confirmation prompt
cx help Getting-started guide
cx <conda-args> Passed through to conda
Disabled commands
cx uses conda-spawn instead of traditional shell-based activation. The following commands are intentionally disabled:
| Command | Instead |
|---|---|
conda activate / deactivate |
cx shell myenv |
conda init |
Add condabin to your PATH (see below) |
Frozen base prefix
The ~/.cx prefix is protected with a CEP 22 frozen marker after bootstrap. This prevents accidental modification of the base environment (e.g., conda install numpy into base). Users should create named environments for their work:
Updating the base installation is handled by conda self update (via conda-self).
Building custom cx binaries
You can build a cx binary with your own set of packages using the composite GitHub Action or the reusable workflow.
Composite action (uses: jezdez/conda-express@main)
Use in a step within your own workflow. You control the platform matrix:
jobs:
build:
strategy:
matrix:
os:
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 }}
Reusable workflow
Builds all 5 platforms in one call:
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"
Build-time environment variables
When building from source, you can override the package configuration without editing pixi.toml:
| Variable | Overrides | Format |
|---|---|---|
CX_PACKAGES |
[tool.cx].packages |
Comma-separated match specs |
CX_CHANNELS |
[tool.cx].channels |
Comma-separated channel names |
CX_EXCLUDE |
[tool.cx].exclude |
Comma-separated package names |
CX_PACKAGES="python >=3.12, conda >=25.1, numpy"
Uninstalling
To completely remove cx, the conda prefix, all environments, and the cx binary:
This will show what will be removed and ask for confirmation. Use --yes to skip the prompt. The command also cleans up PATH entries from shell profiles that were added by the installer.
How it works
-
Compile time:
build.rsreads[tool.cx]frompixi.toml, solves dependencies using rattler, filters excluded packages, and writes a rattler-lock v6 lockfile embedded into the binary. -
First run: cx parses the embedded lockfile, downloads packages from conda-forge, and installs them into the prefix. No repodata fetch or solve needed at runtime.
-
Subsequent runs: cx detects the existing prefix and replaces its own process with the installed
condabinary, passing all arguments through.
Activation model
cx ships with conda-spawn instead of traditional conda activate. There is no need to run conda init or modify shell profiles.
# Add cx to PATH (one-time setup)
# Activate an environment (spawns a subshell)
# Deactivate by exiting the subshell
Lockfile format
The embedded lockfile uses the rattler-lock v6 format (same as pixi.lock). It can be:
- Read by pixi
- Imported by conda-lockfiles
- Checked into version control for reproducibility auditing
License
BSD 3-Clause. See LICENSE.