
September 12, 2026
10 min read
By Kokil Thapa | Last reviewed: September 2026
You need to manage tool versions with asdf and mise when every client project runs a different PHP, Node.js, or Ruby stack. One repo wants PHP 8.3 and Laravel 12. The next needs PHP 8.5 with Node.js 26 LTS for a Vite 8.x build. System packages or a single global install break quickly. I've hit this on Linux server work and local dev alike. Version managers solve the mismatch before deploy day.
.tool-versions or mise.toml file per repo, and run asdf install or mise install so every developer and CI job uses the same pinned binaries.What is the difference between asdf and mise for managing tool versions?
Both tools read a small config file in your project root. They download the exact runtime you specify. They put it on your PATH only inside that directory tree. The idea is borrowed from Ruby's rbenv and extended to many languages.
asdf is the older, plugin-driven option. You install a core CLI, then add one plugin per tool. Each plugin wraps upstream installers. The community maintains hundreds of plugins for PHP, Node.js, Python, Ruby, Terraform, and more.
mise (formerly rtx) is a Rust rewrite with the same mental model. It ships faster core releases, built-in backends for common tools, and optional task running. Many teams migrate from asdf because plugin maintenance varies.
| Criteria | asdf | mise |
|---|---|---|
| Config file | .tool-versions | mise.toml or .tool-versions |
| Plugin model | Separate plugin repo per tool | Core backends + optional plugins |
| Shell hook | asdf.sh in bash/zsh rc | mise activate one-liner |
| Speed | Depends on plugin quality | Generally faster installs |
| Extra features | Version listing only | Task runner, env vars, monorepo roots |
| Best for | Teams already on asdf | New projects and CI-heavy workflows |
Neither tool replaces your OS package manager for system libraries. They manage language runtimes and CLI tools you invoke in development. Production servers often still use apt, systemd-managed PHP-FPM, or container images. The win is parity between laptop and pipeline.
How do you install and configure asdf on Linux or macOS?
Start with the official install path from asdf getting started docs. On Ubuntu 24.04 I prefer the git clone method. It keeps upgrades predictable on shared dev boxes.
Install asdf core
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
echo '. "$HOME/.asdf/completions/asdf.bash"' >> ~/.bashrc
source ~/.bashrc
asdf --version
Add plugins and install runtimes
For a typical Laravel web stack, you need PHP, Composer, and Node.js. Add plugins once per machine. Install versions per project.
- Add the PHP plugin:
asdf plugin add php https://github.com/asdf-community/asdf-php.git - Add Node.js:
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git - Add Composer:
asdf plugin add composer - Install build deps on Ubuntu:
sudo apt install -y build-essential autoconf libssl-dev libxml2-dev - Run
asdf installinside a repo that already has.tool-versions
Create .tool-versions at the project root:
php 8.5.0
nodejs 26.0.0
composer 2.10.0
Run asdf install. asdf reads the file and installs anything missing. When you cd into the directory, shims redirect php, node, and composer to those builds.
Global vs local versions
asdf global php 8.5.0 writes to ~/.tool-versions. That becomes your laptop default. Project files override globals. I keep globals on a stable PHP 8.4 build and pin stricter versions per client repo.
A common mistake is forgetting plugin update commands after pulling a teammate's branch. Run asdf install whenever .tool-versions changes. Add that line to your onboarding doc next to composer install.
How do you set up mise to manage PHP, Node.js, and other runtimes?
mise installs from the official script documented at mise.jdx.dev. Enable shell activation once. After that, config files drive everything.
Install mise and activate it
curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
source ~/.bashrc
mise --version
Pin tools with mise.toml
mise accepts legacy .tool-versions files. Many teams prefer mise.toml because it supports environment variables and tasks in one file.
[tools]
php = "8.5.0"
node = "26"
composer = "2.10"
[env]
APP_ENV = "local"
[tasks.dev]
run = "php artisan serve"
Run mise install to fetch missing builds. Run mise exec -- php -v when you need a one-off command without changing shells. That pattern helps in GitLab CI and GitHub Actions alike.
Trust and security
mise asks you to trust config files before executing tasks or env blocks. Run mise trust in the repo root after cloning. This blocks surprise task definitions from untrusted forks. Treat it like reviewing a Makefile before make deploy.
For WordPress shops, pin PHP and Node together. PHP runs the site. Node builds block themes or Vite bundles. On a WordPress project, a typical pin might be PHP 8.3 with Node.js 24 LTS if the host has not moved to 26 yet. Document the production target in README.
Which tool should you pick for Laravel, WordPress, and CI pipelines?
Pick based on team habit and pipeline complexity. Not based on Twitter polls. Both tools solve the same core problem.
Laravel and PHP-heavy stacks
Laravel 13 needs PHP 8.3 or higher. Laravel 12 runs on PHP 8.2+. I pin the exact patch in .tool-versions so local matches staging. On the Adventure Third Pole Trek stack, Laravel 12, Livewire, and Vite share one repo. PHP and Node must align with what Deployer expects on the server.
Pair version pins with Composer lock discipline. Changing PHP without running tests invites subtle extension gaps. Enable ext-intl, ext-redis, and others before composer install fails halfway.
WordPress and WooCommerce
WordPress 7.1 runs on PHP 8.2 or newer. WooCommerce 11.1 follows the same floor. Local PHP 8.5 with production PHP 8.3 is usually fine. Major jumps are not. Pin conservatively when shared hosting lags behind.
CI/CD parity
Install the same manager in CI. Cache the installs directory. Example GitHub Actions step with mise:
- name: Install mise
uses: jdx/mise-action@v2
- name: Install tools
run: mise install
- name: Run tests
run: mise exec -- php artisan test
GitLab CI mirrors this with a before_script block. The goal matches what I use on sister sites with Deployer 7. Build assets with the same Node LTS that compiled them locally. See pipeline hardening patterns for how env files stay separate from runtime pins.
- Commit
.tool-versionsormise.tomlto git — never gitignore it - Document required system libraries in README for PHP builds
- Run
asdf installormise installin CI before Composer and npm - Align Node major with Vite 8.x or webpack requirements from package.json
- Keep production PHP-FPM version documented beside the pin file
How do you pin tool versions for a team with shared config files?
Standardize one file format per organisation. Mixed repos confuse juniors and break CI caches.
Monorepo and multi-app roots
mise supports nested roots with mise.toml in subfolders. asdf walks upward and picks the nearest file. For an agency maintaining law portals and eCommerce builds, each deployable app gets its own pin file. Shared tooling docs live in an internal wiki linked from dotfiles repos.
Migration from asdf to mise
mise reads existing .tool-versions unchanged. Export plugin list with asdf plugin list. Install equivalents with mise use --global for each tool. Run side by side on one machine before flipping team defaults. Validate with php -v, node -v, and a full test suite.
When not to use either tool
Skip version managers on production app servers that run one PHP version for every vhost. Use apt or remi packages there. Use Docker when the whole stack must be byte-identical. Use asdf or mise when humans juggle many repos daily.
Validate pins against real targets. A JSON formatter won't catch a PHP mismatch. An integration test will. For infrastructure pins like Terraform, see provider version pinning — same discipline, different file.
Onboarding checklist for new hires:
- Install asdf or mise per team standard
- Clone repo and run install command
- Run
composer installandnpm ci - Copy
.env.exampleand set local DB credentials - Run test suite before first pull request
Store expensive build env hints in README. Note OpenSSL versions, icu packages, and whether Apple Silicon needs Rosetta for older Node builds. These details save hours across a Nepal dev team where machine setups vary widely.
Cost is free. Time saved shows up on the first avoided "works on my machine" deploy. For client work under support retainers, document pins during handover. Future you will thank present you.
Key Takeaways
- Commit
.tool-versionsormise.tomlso every clone gets the same PHP, Node.js, and Composer builds. - Run
asdf installormise installafter every pull that touches the pin file. - Mirror pins in CI with the same manager — never depend on whatever
phpthe runner ships. - Pick mise for greenfield repos; stay on asdf when plugins already work and the team knows them.
- Keep production PHP-FPM and local pins documented side by side in README.
- Pair runtime pins with locked Composer and npm manifests for true stack reproducibility.
People Also Ask
Can asdf and mise manage the same .tool-versions file?
Yes. Both read the plain .tool-versions format. Lines list a tool name and a version string. mise also accepts aliases like node = "lts" inside mise.toml. Teams migrating often keep .tool-versions for compatibility during a transition window.
Does mise replace Docker for local development?
No. mise swaps language binaries on your host. Docker packages the OS, extensions, and services together. Use mise for fast daily coding across many repos. Use Docker when you need full stack isolation or to match a Kubernetes production image exactly.
Which PHP version should I pin for Laravel 13 in 2026?
Pin PHP 8.3 at minimum. PHP 8.5 is the current anchor for new work. Match whatever runs on your staging server. Laravel 13 requires PHP 8.3+. Laravel 12 still supports PHP 8.2 if you maintain legacy apps.
How do I fix "plugin not installed" errors in asdf?
Run asdf plugin list to see what is missing. Add the plugin with the official URL from the plugin repo readme. Then run asdf install again. Plugin drift is the top reason installs fail after a fresh laptop setup.
Ship consistent stacks across every repo
Learning to manage tool versions with asdf and mise pays off the first time staging matches your laptop without a midnight SSH session. Pick one manager, pin honestly, wire CI, and treat the config file as part of your application source. If you want help aligning dev environments with production on a Laravel, WordPress, or multi-site portfolio, reach out for a technical review or browse the legal-tech projects and custom software work for examples of pinned stacks in the wild.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

