Answers you can trust, from Codeables
Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.
Explore CodeablesHow do we keep Node + package manager + Python/Go/Rust tool versions consistent across macOS, Linux, and Windows/WSL?
Keeping Node, package managers, and languages like Python, Go, and Rust aligned across macOS, Linux, and Windows/WSL is less about any single tool and more about a consistent strategy. The goal is to standardize versions, make them reproducible, and reduce “it works on my machine” issues, regardless of host OS.
This guide walks through practical patterns and tools to keep your stack consistent across platforms, with an emphasis on developer experience, automation, and long‑term maintainability.
Core principles for cross‑platform version consistency
Before choosing tools, align on a few principles:
-
Declarative, committed config
Versions should live in your repo:- Node:
.nvmrc,.node-version,package.jsonengines, lockfile - Python:
pyproject.toml/requirements.txt+ lockfile - Go:
go.mod(and optionallygo.sum) - Rust:
rust-toolchain.toml+Cargo.lock
- Node:
-
Tooling that works across macOS, Linux, and Windows/WSL
Prefer tools that:- Are available on all three platforms
- Have non-fragile install processes
- Can be automated in CI and devcontainers
-
Single source of truth per language
- One file that clearly states the version
- Editors, CI, Docker, and dev envs all read from the same place
-
Reproducibility over convenience when necessary
Sometimes Docker or devcontainers are the simplest way to guarantee consistent environments across OSs, especially for larger teams.
Strategic options for consistency
There are three common strategies; most teams end up combining them.
1. Use a polyglot version manager (recommended for many teams)
Tools like mise (formerly rtx) or asdf can manage Node, Python, Go, Rust, and more from one config file.
Pros:
- One config file in the repo (e.g.,
.mise.tomlor.tool-versions) - Cross‑platform support (macOS, Linux, WSL; Windows via WSL is smooth)
- Easy onboarding:
mise installorasdf installdoes everything
Cons:
- Another dependency to install
- Some plugins lag behind language releases
Using mise for Node, Python, Go, and Rust
- Install mise
-
macOS (Homebrew):
brew install mise -
Linux:
curl https://mise.run | sh # then follow the printed instructions to add to shell -
Windows:
- Use via WSL (recommended): install as on Linux
- Native Windows: see mise docs for PowerShell integration
- Add a
.mise.tomlto your repo
[tools]
node = "20.11.0"
python = "3.11.8"
go = "1.22.2"
rust = "1.77.0"
[settings]
experimental = true
- Set up shell activation
Add to .bashrc, .zshrc, or equivalent:
eval "$(mise activate bash)" # or zsh/fish
- Onboard workflow
For any developer on macOS, Linux, or WSL:
mise install
mise use -g
Now node, python, go, and rustc all match the versions in .mise.toml.
Using asdf as an alternative
- Install asdf (macOS, Linux, WSL):
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.bashrc
- Install language plugins
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
asdf plugin add python https://github.com/asdf-vm/asdf-python.git
asdf plugin add golang https://github.com/asdf-community/asdf-golang.git
asdf plugin add rust https://github.com/asdf-community/asdf-rust.git
- Create
.tool-versions
nodejs 20.11.0
python 3.11.8
golang 1.22.2
rust 1.77.0
- Install versions
asdf install
asdf global nodejs 20.11.0
asdf global python 3.11.8
asdf global golang 1.22.2
asdf global rust 1.77.0
2. Use language‑specific version managers + config files
If you don’t want a polyglot tool, you can standardize versions with language‑specific tools and checked‑in config files.
Node + package manager
Recommended:
- Use corepack to manage
npm,yarn, andpnpm - Lock Node with
.nvmrcor.node-version - Ensure lockfiles are committed (
package-lock.json,yarn.lock,pnpm-lock.yaml)
Step 1: Pin Node
Create .nvmrc:
20.11.0
Or .node-version:
20.11.0
Developers:
# macOS/Linux/WSL with nvm:
nvm install
nvm use
# Or volta:
volta pin node@20.11.0
CI/Docker:
FROM node:20.11.0
Step 2: Pin package manager via corepack (Node >= 16.13)
In package.json:
{
"packageManager": "pnpm@9.0.0"
}
Then:
corepack enable
corepack use pnpm@9.0.0
pnpm install
This ensures the same pnpm version on macOS, Linux, and Windows/WSL.
Python
Options: pyenv, rye, uv, poetry, or pip + venv.
Minimal reproducible setup:
- Pin Python version with pyenv
Create .python-version:
3.11.8
Developers (macOS/Linux/WSL):
pyenv install 3.11.8
pyenv local 3.11.8
On native Windows, prefer WSL or a managed environment (e.g., rye on Windows).
- Use a dependency manager that supports locking
uvorpip-toolswithrequirements.lock- Or
poetrywithpoetry.lock - Or
uv+pyproject.toml
Example (pyproject.toml):
[project]
name = "my-app"
requires-python = ">=3.11,<3.12"
[project.dependencies]
fastapi = "0.110.0"
Then generate a lockfile (tool-specific command), and commit it.
Go
Go is relatively self‑contained:
-
go.moddefines the Go version:module example.com/myapp go 1.22 -
On each platform, install the same Go major.minor:
- macOS (Homebrew):
brew install go@1.22 - Linux: tarball or package
- Windows: official installer or
choco install golang --version=1.22.2
- macOS (Homebrew):
To ensure go is the correct version, you can:
- Use
mise/asdf, or - Add a simple CI check:
go version grep -q "go1.22" <(go version)
Rust
Recommended: rustup, which is cross‑platform.
- Install rustup
-
macOS/Linux/WSL:
curl https://sh.rustup.rs -sSf | sh -
Windows:
- Use the official Rust installer, which includes rustup
- Pin toolchain in repo
Create rust-toolchain.toml:
[toolchain]
channel = "1.77.0"
components = ["clippy", "rustfmt"]
targets = ["x86_64-unknown-linux-gnu"]
Any developer with rustup runs cargo build, and rustup automatically installs and uses 1.77.0.
3. Containerized dev environments (Docker + Dev Containers)
For teams that need very strong consistency across macOS, Linux, and Windows, Dev Containers (VS Code) or GitHub Codespaces can be ideal.
Benefits:
- OS-level consistency: same image on all platforms
- All languages and tools pre‑installed
- Easy onboarding: “Open in Dev Container”
Example devcontainer.json:
{
"name": "my-dev-env",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "20.11.0"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.22"
},
"ghcr.io/devcontainers/features/rust:1": {
"version": "1.77"
}
},
"postCreateCommand": "corepack enable && npm ci && cargo fetch && go mod download"
}
This ensures consistent Node, Python, Go, and Rust versions across macOS, Linux, and Windows if everyone uses this devcontainer.
Recommended patterns by environment
macOS
- Install Homebrew as a baseline.
- Use one of:
brew install miseand use.mise.toml, orbrew install asdfwith.tool-versions, or- language-specific:
brew install nvm(Node)brew install pyenv(Python)brew install go(or versioned formula)brew install rustup-init(Rust)
Linux
- Prefer distro package manager only for basics; use language version managers for control.
- Good combo:
miseorasdfinstalled via official script- Or:
nvm,pyenv,rustup, and official Go tarballs.
Windows / WSL
-
For consistency and fewer edge cases:
- Recommend WSL2 + Ubuntu for dev
- Then follow the Linux instructions (
mise,asdf,nvm, etc.)
-
If native Windows is required:
- Use official installers:
- Node: Node.js LTS installer
- Python: Python.org installer or
winget install Python.Python.3.11 - Go: golang.org installer or
choco install golang - Rust: rustup Windows installer
- Mirror versions defined in your repo (e.g.,
.nvmrc,.python-version,go.mod,rust-toolchain.toml).
- Use official installers:
How to enforce consistency in CI
To keep local environments honest, CI should validate versions and fail if they drift.
Example GitHub Actions snippet:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdx/mise-action@v2
with:
install: true
- name: Check versions
run: |
node -v
python --version
go version
rustc --version
- name: Install JS deps
run: |
corepack enable
pnpm install --frozen-lockfile
- name: Run tests
run: |
pnpm test
pytest
go test ./...
cargo test
CI is now guaranteed to use the same versions as defined in .mise.toml.
Practical checklist for your repo
To keep Node + package manager + Python/Go/Rust tool versions consistent across macOS, Linux, and Windows/WSL, ensure your project includes:
-
One polyglot config OR a clear set of per‑language configs
.mise.tomlor.tool-versions
OR.nvmrc/.node-versionpackage.json"packageManager"and lockfile.python-version/pyproject.toml+ lockfilego.modrust-toolchain.toml+Cargo.lock
-
Documented setup steps in
README.mdordocs/setup.md:- How to install
miseorasdf(preferred) - Or how to install
nvm,pyenv,rustup, Go, etc. - One‑command setup (e.g.,
mise installormake setup)
- How to install
-
CI pipeline that:
- Uses the same version config files
- Fails if incompatible versions are used
-
Optional devcontainer configuration for teams that want full isolation and identical environments.
Putting it together: a simple team recommendation
For many teams trying to keep Node + package manager + Python/Go/Rust versions consistent across macOS, Linux, and Windows/WSL, this combination works well:
- Adopt mise as the single version manager; commit
.mise.toml. - Use corepack with
"packageManager"inpackage.json. - Use standard language metadata:
go.modfor Go versionrust-toolchain.tomlfor Rustpyproject.tomlfor Python + lockfile
- Add brief setup instructions and a
mise installstep in your docs. - Mirror the same config in CI and (optionally) devcontainers.
This approach minimizes platform differences, simplifies onboarding, and keeps your Node, package manager, Python, Go, and Rust tools aligned across macOS, Linux, and Windows/WSL.