Skip to content
Latchkey

pyenv: Install a Python and the Build Deps CI Needs

pyenv install builds CPython from source, so a CI image missing libssl-dev/zlib headers fails the build; install the documented build deps first.

pyenv compiles each Python version rather than downloading a prebuilt binary. That makes it portable but means the runner needs a C toolchain and several -dev packages, the absence of which is the classic pyenv CI failure.

What it does

pyenv manages multiple Python versions via shims. pyenv install <v> downloads the CPython source and compiles it under ~/.pyenv/versions. pyenv global/pyenv local write the active version, and shims on PATH dispatch python to it.

Common usage

bash
# Debian/Ubuntu build deps (the part CI forgets)
sudo apt-get update && sudo apt-get install -y \
  make build-essential libssl-dev zlib1g-dev libbz2-dev \
  libreadline-dev libsqlite3-dev libffi-dev liblzma-dev
export PYENV_ROOT="$HOME/.pyenv"; export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
pyenv install 3.12.3
pyenv global 3.12.3
python --version

Options

Command / flagWhat it does
pyenv install <v>Compile and install a Python version
pyenv install -s <v>Skip if already installed (safe in CI)
pyenv global <v>Set the default version
pyenv local <v>Write .python-version for this directory
eval "$(pyenv init -)"Wire up shims and PATH in the shell
PYTHON_CONFIGURE_OPTSPass flags like --enable-shared to the build

In CI

Run eval "$(pyenv init -)" (and pyenv init --path) in each step so shims are on PATH, since PATH does not persist across steps. Cache ~/.pyenv/versions to skip recompiling. Use pyenv install -s so a re-run does not rebuild an existing version.

Common errors in CI

"ModuleNotFoundError: No module named '_ssl'" or "The Python ssl extension was not compiled. Missing the OpenSSL lib?" means libssl-dev was absent at build time; install it and rebuild. "zipimport.ZipImportError: can't decompress data; zlib not available" means zlib1g-dev was missing. "pyenv: python: command not found" with a shim present usually means pyenv rehash is needed or init was not eval'd.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →