Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Manage Tool Versions with asdf and mise

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.

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.

Manage Tool Versions: asdf vs miseasdfPlugin per runtime.tool-versions fileCommunity pluginsmiseCore + backendsmise.toml or .tool-versionsTasks + faster installsShared outcomePer-project PATH shimsSame PHP / Node / Ruby per repo
asdf and mise both pin runtimes per project — plugins vs built-in backends is the main split
Criteriaasdfmise
Config file.tool-versionsmise.toml or .tool-versions
Plugin modelSeparate plugin repo per toolCore backends + optional plugins
Shell hookasdf.sh in bash/zsh rcmise activate one-liner
SpeedDepends on plugin qualityGenerally faster installs
Extra featuresVersion listing onlyTask runner, env vars, monorepo roots
Best forTeams already on asdfNew 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.

  1. Add the PHP plugin: asdf plugin add php https://github.com/asdf-community/asdf-php.git
  2. Add Node.js: asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
  3. Add Composer: asdf plugin add composer
  4. Install build deps on Ubuntu: sudo apt install -y build-essential autoconf libssl-dev libxml2-dev
  5. Run asdf install inside 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.

asdf Shim Resolution FlowShellShim~/.asdf/shims.tool-versionsWalk up treeBinaryExample: php artisan migrate1. Shim intercepts php2. Finds .tool-versions in /var/www/client-app3. Runs php 8.5.0 from ~/.asdf/installs/php/4. Wrong version in parent dir? Child file wins.
asdf shims walk up the directory tree to find the nearest .tool-versions file before executing the real binary

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.

mise Project Workflowmise.tomlPin PHP + Nodemise installDownload runtimesmise trustApprove configDeveloper commandscomposer installnpm install (npm 12)npm run build (Vite 8.x)php artisan test
mise install and mise trust precede everyday Laravel and frontend build commands in a pinned workspace

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-versions or mise.toml to git — never gitignore it
  • Document required system libraries in README for PHP builds
  • Run asdf install or mise install in 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
Pick asdf or mise?New project in 2026?Try miseTasks + speedKeep asdfExisting plugins OKBoth need: committed pin file + CI install stepMatch production PHP-FPM and Node LTS targetsNever rely on system php or node in pipelines
Decision guide to manage tool versions with asdf and mise — new repos often start with mise, mature asdf shops can stay put

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:

  1. Install asdf or mise per team standard
  2. Clone repo and run install command
  3. Run composer install and npm ci
  4. Copy .env.example and set local DB credentials
  5. 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-versions or mise.toml so every clone gets the same PHP, Node.js, and Composer builds.
  • Run asdf install or mise install after every pull that touches the pin file.
  • Mirror pins in CI with the same manager — never depend on whatever php the 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

They are per-project runtime managers. Each reads a small config file in your repo root, downloads the exact PHP, Node.js, or Composer build you specify, and puts those binaries on PATH only inside that directory tree. The goal is laptop-and-CI parity so one client repo can run PHP 8.3 with Laravel 12 while the next needs PHP 8.5 and Node.js 26 LTS for a Vite 8.x build without fighting system packages.

Both pin runtimes per project using the same mental model borrowed from rbenv. asdf is the older plugin-driven option: you install a core CLI, then add a separate community plugin per tool. mise is a Rust rewrite with built-in backends for common tools, generally faster installs, and optional task running and env vars in mise.toml. asdf uses .tool-versions; mise accepts that file or mise.toml. Teams already comfortable with asdf plugins often stay put; new repos and CI-heavy workflows frequently start with mise because plugin maintenance varies.

Start from the official asdf getting started docs. On Ubuntu 24.04 I prefer git clone into ~/.asdf at tag v0.14.1, then source asdf.sh and completions in bashrc. Add plugins once per machine: asdf-community/asdf-php for PHP, asdf-vm/asdf-nodejs for Node.js, and a Composer plugin. Ubuntu PHP builds need build-essential, autoconf, libssl-dev, and libxml2-dev. Create .tool-versions at the project root listing each tool and version, then run asdf install. Shims redirect php, node, and composer to those builds when you cd into the repo.

Install from the official script at mise.jdx.dev, then add mise activate to your shell rc file. Pin tools in mise.toml under a [tools] block, for example php 8.5.0, node 26, and composer 2.10. You can also add [env] variables and [tasks] like a dev server command in one file. Run mise install to fetch missing builds. Use mise exec -- php -v for one-off commands without reloading your shell, which is handy in GitLab CI and GitHub Actions. After cloning, run mise trust in the repo root before tasks or env blocks execute.

Pick based on team habit and pipeline complexity, not hype. Both solve the same core problem. For Laravel, pin PHP to match staging: Laravel 13 needs PHP 8.3+, Laravel 12 runs on PHP 8.2+. Pair pins with Composer lock discipline and required extensions like ext-intl and ext-redis before composer install. WordPress 7.1 and WooCommerce 11.1 need PHP 8.2+; pin conservatively when shared hosting lags. In CI, install the same manager, cache the installs directory, and run mise install or asdf install before Composer and npm ci so assets build with the same Node LTS that compiled them locally.

Both are free open-source CLIs with no licensing fees. Your only cost is initial setup time per machine and plugin maintenance for asdf shops.

Yes. Both read the plain .tool-versions format where each line lists a tool name and a version string. mise also accepts aliases like node = "lts" inside mise.toml, and many teams prefer that format because it supports environment variables and tasks in one file. Teams migrating from asdf to mise often keep .tool-versions unchanged during a transition window since mise reads legacy files without modification. Standardise one file format per organisation though, because mixed repos confuse juniors and break CI cache assumptions.

No. mise swaps language binaries on your host machine while Docker packages the OS, extensions, and services together into an isolated image. Use mise when developers juggle many repos daily and need fast context switching between PHP and Node versions. Use Docker when the whole stack must be byte-identical to production or when you need to match a Kubernetes image exactly. Neither tool replaces apt or systemd-managed PHP-FPM on production servers that run one PHP version for every vhost. The win with asdf or mise is parity between laptop and pipeline, not full container isolation.

Pin PHP 8.3 at minimum. PHP 8.5 is the current anchor for new work; match your staging server patch exactly.

Run asdf plugin list to see what is missing on your machine. Add each plugin with the official URL from the plugin repo readme, for example asdf-community/asdf-php for PHP or asdf-vm/asdf-nodejs for Node.js. Then run asdf install again inside the repo. Plugin drift is the top reason installs fail after a fresh laptop setup or when a teammate adds a new runtime to .tool-versions without updating onboarding docs. Add asdf install to your checklist next to composer install whenever someone pulls a branch that touches the pin file.

Running asdf global php 8.5.0 writes to ~/.tool-versions and sets your laptop default outside any project. A .tool-versions file in a repo root overrides globals for that directory tree. asdf shims walk up the directory tree to find the nearest pin file before executing the real binary. I keep globals on a stable PHP 8.4 build and pin stricter versions per client repo. The same override logic applies when mise reads mise.toml or .tool-versions in nested monorepo subfolders. Document your production PHP-FPM target beside the pin file so globals never drift too far from deploy reality.

Install the same version manager in CI that developers use locally. Cache the installs directory between runs. In GitHub Actions, use jdx/mise-action@v2, then run mise install before tests with mise exec -- php artisan test. GitLab CI mirrors this in a before_script block. Never depend on whatever php the runner ships by default. Commit .tool-versions or mise.toml to git and never gitignore it. Run asdf install or mise install before Composer and npm ci. Align the Node major with Vite 8.x or webpack requirements from package.json. This matches the Deployer 7 workflow I use on sister sites where build assets must compile with the same Node LTS locally and in the pipeline.

mise asks you to trust config files before executing tasks or env blocks defined in mise.toml. Run mise trust in the repo root after cloning. This blocks surprise task definitions from untrusted forks or copied repos from running arbitrary commands on your machine. Treat it like reviewing a Makefile before make deploy. The trust step precedes everyday Laravel and frontend build commands in a pinned workspace. asdf has no equivalent gate because it focuses on version listing rather than task running. If your team adopts mise tasks for dev servers or deploy helpers, trust becomes part of standard onboarding alongside mise install.

Skip version managers on production app servers that run one PHP version for every vhost; use apt or remi packages there instead. Use Docker when the whole stack must be byte-identical across every environment. Use asdf or mise when humans juggle many repos daily with different PHP, Node, or Ruby stacks. Neither tool replaces your OS package manager for system libraries; they manage language runtimes and CLI tools you invoke in development. Validate pins against real targets because a JSON formatter will not catch a PHP mismatch, but an integration test will. For infrastructure tools like Terraform, apply the same pinning discipline in a different config file.

mise reads existing .tool-versions unchanged, so you can run both side by side on one machine before flipping team defaults. Export your current plugin list with asdf plugin list, then install equivalents with mise use --global for each tool. Validate with php -v, node -v, and a full test suite on every active repo. Many teams keep .tool-versions during a transition window rather than converting everything to mise.toml immediately. Run side-by-side validation on a Laravel stack with Livewire and Vite before changing CI. Once confident, update onboarding docs and pipeline steps to call mise install and mise trust instead of asdf install.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: