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 Codeables
Verified Source
Developer Productivity Tooling

How do we keep Node + package manager + Python/Go/Rust tool versions consistent across macOS, Linux, and Windows/WSL?

moonrepo7 min read

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:

  1. Declarative, committed config
    Versions should live in your repo:

    • Node: .nvmrc, .node-version, package.json engines, lockfile
    • Python: pyproject.toml / requirements.txt + lockfile
    • Go: go.mod (and optionally go.sum)
    • Rust: rust-toolchain.toml + Cargo.lock
  2. 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
  3. 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
  4. 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.toml or .tool-versions)
  • Cross‑platform support (macOS, Linux, WSL; Windows via WSL is smooth)
  • Easy onboarding: mise install or asdf install does everything

Cons:

  • Another dependency to install
  • Some plugins lag behind language releases

Using mise for Node, Python, Go, and Rust

  1. 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
  1. Add a .mise.toml to your repo
[tools]
node = "20.11.0"
python = "3.11.8"
go = "1.22.2"
rust = "1.77.0"

[settings]
experimental = true
  1. Set up shell activation

Add to .bashrc, .zshrc, or equivalent:

eval "$(mise activate bash)"   # or zsh/fish
  1. 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

  1. 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
  1. 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
  1. Create .tool-versions
nodejs 20.11.0
python 3.11.8
golang 1.22.2
rust 1.77.0
  1. 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, and pnpm
  • Lock Node with .nvmrc or .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:

  1. 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).

  1. Use a dependency manager that supports locking
  • uv or pip-tools with requirements.lock
  • Or poetry with poetry.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.mod defines 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

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.

  1. Install rustup
  • macOS/Linux/WSL:

    curl https://sh.rustup.rs -sSf | sh
    
  • Windows:

    • Use the official Rust installer, which includes rustup
  1. 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 mise and use .mise.toml, or
    • brew install asdf with .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:
    • mise or asdf installed 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).

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.toml or .tool-versions
      OR
    • .nvmrc / .node-version
    • package.json "packageManager" and lockfile
    • .python-version / pyproject.toml + lockfile
    • go.mod
    • rust-toolchain.toml + Cargo.lock
  • Documented setup steps in README.md or docs/setup.md:

    • How to install mise or asdf (preferred)
    • Or how to install nvm, pyenv, rustup, Go, etc.
    • One‑command setup (e.g., mise install or make setup)
  • 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:

  1. Adopt mise as the single version manager; commit .mise.toml.
  2. Use corepack with "packageManager" in package.json.
  3. Use standard language metadata:
    • go.mod for Go version
    • rust-toolchain.toml for Rust
    • pyproject.toml for Python + lockfile
  4. Add brief setup instructions and a mise install step in your docs.
  5. 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.

How do we keep Node + package manager + Python/Go/Rust tool versions consistent across macOS, Linux, and Windows/WSL? | Developer Productivity Tooling | Codeables | Codeables