Mastering Travis CI Build Matrices: One YAML, Multiple OS & Python Versions
Use a single .travis.yml to test your Python project on Linux and macOS with Python 3.9 and 3.11. Learn how the matrix expands, how to include/exclude combos, and avoid common pitfalls like cache mis‑use and matrix explosions.
01 Apr 2026, 17:22 UTC

Why a Build Matrix Matters
When your library must run on several Python versions and operating systems, writing separate .travis.yml files for each combination is tedious and error‑prone. Travis CI’s build matrix lets you declare a set of axes—Python versions, OSes, or custom env vars—and automatically creates the Cartesian product of those axes. Each product becomes an independent job that runs in parallel, so you get fast, isolated feedback on every supported combination.
How the Matrix Is Built
The matrix is defined under the matrix key. Travis expands the list of values for each axis and pairs them. For example, python: [3.9, 3.11] and os: [linux, osx] produce four jobs:
- Linux + Python 3.9
- Linux + Python 3.11
- macOS + Python 3.9
- macOS + Python 3.11
install and script steps but runs in its own environment. Travis automatically exposes the chosen values via environment variables such as TRAVIS_PYTHON_VERSION and TRAVIS_OS_NAME, which you can use for conditional logic inside the job.
Worked Example: A Minimal Python Project
Below is a complete .travis.yml that tests a tiny Python package on both Linux and macOS with two Python versions. It also shows how to add a custom environment variable and exclude a problematic combination.
language: python
python:
- "3.9"
- "3.11"
os:
- linux
- osx
env:
- TOXENV=unit
matrix:
include:
- python: "3.9"
env: TOXENV=integration
exclude:
- python: "3.9"
os: osx # skip macOS with Python 3.9 because of a known binary issue
cache:
pip: true
install:
- pip install --upgrade pip
- pip install tox
script:
- tox -e $TOXENV
Explanation of the key parts:
pythonandosdefine the primary axes.envsets a default environment variable that each job inherits.matrix.includeadds an extra job that runs theintegrationtest suite on Linux with Python 3.9.matrix.excluderemoves the macOS/Python 3.9 combination because a native dependency is broken on that platform.- The
cachesection tells Travis to persist the pip cache per OS and Python version. Note that caches are scoped; a cache hit on Linux 3.9 does not help the macOS job.
Checking the Result in Travis UI
After pushing the file, Travis creates one job card for each matrix cell. In the build overview you’ll see job names like Python 3.9 on Linux or Python 3.11 on macOS. Click a job to view its console log; the first few lines will contain:
Running on Linux
Using Python 3.9
TRAVIS_PYTHON_VERSION=3.9
TRAVIS_OS_NAME=linux
These variables confirm that the job is running the intended combination. If a job fails, the others continue to run, giving you clear isolation of the failure.
Limits to Keep in Mind
- Job Count Quotas – Free and paid Travis plans cap the number of concurrent jobs and total matrix size. Adding more axes (e.g., different Python interpreters, database versions) multiplies the job count. Check your plan’s limits in the Travis dashboard or by contacting support.
- OS‑Specific Dependencies – A script that works on Linux may fail on macOS because of missing packages or different path conventions. Test each job’s log to catch such discrepancies.
- Cache Scope – The pip cache is per language and OS. If you add a new Python version, the cache for that version starts empty, causing a longer install time for the first run.
Common Mistakes and How to Avoid Them
- Assuming Shared Cache – Caches are not shared across OSes or Python versions. If you rely on a pre‑built wheel, it may be missing on a different OS. Use
cache: piponly when you’re sure the wheel is portable. - Mutable Package Indexes – Installing from a public index that changes over time can produce non‑reproducible builds. Pin exact versions in
requirements.txtor use apip‑freezesnapshot. - Matrix Explosion – Adding many axes (e.g.,
python: [3.8, 3.9, 3.10, 3.11]andos: [linux, osx, windows]) creates 12 jobs. If you’re on a limited plan, this may exceed your quota. Usematrix.includeandmatrix.excludeto keep the matrix lean. - Missing Conditional Logic – If a job needs a different command on macOS, forget to check
TRAVIS_OS_NAMEin the script. Add a simple guard:if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then … fi.
Practical Checklist Before You Commit
- Validate the Matrix Size – Count the number of combinations and compare against your plan’s job quota.
- Run a Dry Build – Push the
.travis.ymlto a separate branch and inspect the job list in the Travis UI. - Verify Environment Variables – In each job’s log, confirm that
TRAVIS_PYTHON_VERSIONandTRAVIS_OS_NAMEmatch the expected values. - Check Caching – Look at the log lines that mention “Cache hit” or “Cache miss” to ensure caching behaves as intended.
- Monitor Build Time – If a job is unusually slow, consider moving heavy install steps to a
before_installcache or usingpip‑install‑--no‑cache‑dirfor a fresh install.
By following this guide, you can confidently set up a Travis CI build matrix that covers all your target environments in a single, maintainable configuration file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.