Native, blazing fast, zero-configuration PHP version manager for Arch Linux and other Linux/macOS environments, heavily inspired by fnm.
PVM uses pre-compiled static PHP CLI binaries from Static PHP CLI (SPC) to completely bypass compilation times and library dependency hell on Linux.
- π Blazing Fast: Written in Rust natively. Execution means zero overhead compared to Docker wrappers.
- β¨ Zero Configuration: Auto-switches PHP versions based on
.php-versionfiles. - π¦ Static Binaries: No compilation needed. The
pvm installcommand instantly downloads self-contained executables with most common extensions pre-baked. - π§© Multi-Package Selection: Pick which packages to install per version β
cli,fpm, and/ormicro(micro.sfx) β via an interactive MultiSelect prompt.cliis the default. - π Native Composer Support: Works out of the box with your system's global Composer without any explicit proxy or configuration.
- π±οΈ Interactive TUI Menus: Run
pvmwithout arguments to launch a master selection menu. Or run commands likepvm use/pvm ls/pvm install/pvm uninstallwithout parameters to select actions via a visual UI. - π Persistent Default:
pvm default 8.4makes a version survive new terminals β every fresh shell starts on it viapvm env.pvm use systemswitches back to the system PHP anytime. - π·οΈ Smart Aliasing: Install and use patches cleanly by saying
pvm install 8.4. PVM dynamically figures out the highest patch (8.4.18) underneath the hood. - π Patch Update Check:
pvm use 8.4notices when a newer patch (e.g.8.4.19) is available and offers to install and switch in one step (interactive shells only β scripts never trigger surprise downloads).pvm prunecleans up superseded patches afterwards. - π€ Script-Friendly: every command works without a terminal β
pvm install 8.4 --packages cli,fpm -yinstalls non-interactively,pvm ls/pvm ls-remoteprint plain lists when piped, prompts fall back to sane defaults. - π
.php-versionBootstrap:pvm initinteractively picks a major.minor and writes.php-versionfor the current directory; picker-driven switches offer to save your choice there too. - ποΈ Clean Uninstall:
pvm uninstall(aliasrm/remove) removes a version's binaries; warns when removing the active one. - π Multi-Shell: Bash, Zsh, and Fish wrappers generated by
pvm env, with concurrency-safe per-PID env files under OS file locks. - β‘ Cached Cloud Resolution: Quickly check for new versions on
dl.static-php.devunder lightning-fast 24-hour JSON caching (pvm cache clearforces a refresh).
We provide an automatic install script that detects your platform exactly like fnm and downloads the pre-compiled native pvm binary directly from GitHub Releases into ~/.local/share/pvm/bin, and then instructs you how to append the hook to your profile.
Using a script (macOS/Linux)
curl -fsSL https://raw.githubusercontent.com/WebProject-xyz/php-version-manager/main/install.sh | bashBuilding from Source If you prefer to compile the application from scratch using Rust:
git clone git@github.com:WebProject-xyz/php-version-manager.git
cd php-version-manager
chmod +x build.sh
./build.sh# Enter the master interactive TUI menu
pvm
# Install a specific PHP version (by minor alias or fully-qualified).
# Opens a MultiSelect to pick packages: cli (default), fpm, micro.
# Without a version argument, opens an interactive remote-version picker.
pvm install 8.4 # alias: pvm i 8.4
# Non-interactive install for scripts/CI: pick packages up front, skip prompts.
pvm install 8.4 --packages cli,fpm -y
# Install the absolute latest version available
pvm install latest
# Use a version in the current shell.
# Auto-prompts to install + switch if a newer patch exists upstream.
pvm use 8.4
# Switch this shell back to the system PHP
pvm use system
# Make a version the default for every new shell (cleared via: pvm default system)
pvm default 8.4
# List installed versions. On a terminal this is a picker: Enter switches
# (and offers to save the choice to .php-version), Esc just exits.
# When piped it prints a plain list.
pvm ls # alias for: pvm list
# List available cloud versions (plain list, script-friendly)
pvm ls-remote # alias: pvm list-remote
# Print the currently active PHP version
pvm current
# Print the path of the active (or a given) php binary
pvm which
pvm which 8.3
# Run a single command under a specific version without switching the shell
pvm exec 8.3 php -v
pvm exec 8.3 composer install
# Remove an installed version (interactive picker if no arg).
pvm uninstall 8.3 # aliases: pvm rm 8.3 / pvm remove 8.3
# Remove every patch that is no longer the newest of its minor line
pvm prune # add -y to skip the confirmation
# Write a .php-version file for this directory (interactive picker)
pvm init
# Drop the 24h remote-version cache (e.g. right after an upstream release)
pvm cache clear
# Check for and apply updates to pvm itself
pvm self-update # optional: pvm self-update --apply to apply automaticallyIf you run pvm init or manually create a .php-version file in a project directory containing 8.3, PVM will automatically switch to your best local 8.3.x patch when you cd into that folder. The cd hook is installed via pvm env (Bash, Zsh, or Fish β auto-detected from $SHELL).
Each PHP version can ship up to three binaries; you pick which during pvm install via the MultiSelect prompt. All land under $PVM_DIR/versions/<full-semver>/bin/. Upstream reference for all three SAPIs: static-php.dev β SAPI Reference.
| Package | Binary | What it is |
|---|---|---|
cli (default) |
php |
Standard command-line PHP β runs scripts, REPL via php -a, drives Composer. See SAPI Reference: CLI. |
fpm |
php-fpm |
FastCGI Process Manager for serving PHP behind nginx/Caddy/Apache. Setup details in the next section + SAPI Reference: FPM. |
micro |
micro.sfx |
phpmicro self-contained executable stub β concat with a .php or .phar to ship a single-file PHP app. Combining requires the upstream spc toolchain (spc micro:combine app.phar --output=app); pvm only delivers the stub. See SAPI Reference: Micro. |
After pvm use <version>, every selected binary is on $PATH (CLI as php, FPM as php-fpm); micro.sfx stays at its absolute path since it's a build artifact, not something you invoke directly.
Upstream reference: static-php.dev β SAPI Reference: FPM documents the binary's CLI flags (
-y,-c,-t), a minimalphp-fpm.conf, and an nginx FastCGI block. The guide below extends that with service wiring (systemd / launchd) and pvm-specific paths.
PVM downloads a static php-fpm binary alongside php when you tick the fpm package during pvm install. The static-php-cli tarball ships only the binary β no php-fpm.conf, no pool files, no init script β so you wire those up yourself. The binary lives next to the CLI at:
$PVM_DIR/versions/<full-semver>/bin/php-fpm
$PVM_DIR defaults to ~/.local/share/pvm. After running pvm use 8.4 it is also on $PATH as plain php-fpm.
pvm install 8.4
# When the MultiSelect prompt appears, tick "fpm" (and "cli" if you want both).
pvm use 8.4
php-fpm -v # confirm it resolves to the pvm-managed binary
which php-fpm # β ~/.local/share/pvm/versions/8.4.x/bin/php-fpmPut these under ~/.config/php-fpm/ (any path works β the binary takes -y and -c):
~/.config/php-fpm/php-fpm.conf:
[global]
pid = /tmp/php-fpm.pid
error_log = /tmp/php-fpm.log
daemonize = no
include = /home/YOU/.config/php-fpm/pool.d/*.conf~/.config/php-fpm/pool.d/www.conf:
[www]
user = YOU
group = YOU
listen = 127.0.0.1:9000
; or a unix socket:
; listen = /tmp/php-fpm-www.sock
; listen.owner = YOU
; listen.group = YOU
; listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
catch_workers_output = yes
clear_env = noReplace YOU with your username (whoami).
# Validate config first
php-fpm -y ~/.config/php-fpm/php-fpm.conf -t
# Foreground run, logs to stdout
php-fpm -y ~/.config/php-fpm/php-fpm.conf -F
# With a custom php.ini (the static binary has no compiled-in ini path)
php-fpm -c ~/.config/php-fpm/php.ini -y ~/.config/php-fpm/php-fpm.conf -FFlag summary (matches upstream SAPI Reference: FPM):
-y <file>βphp-fpm.confpath (required, no default for static builds)-c <file>βphp.inipath (optional; without it, fpm runs with hard-coded defaults)-tβ validate config and exit-Fβ stay in foreground (don't fork to daemon)-vβ print version-mβ list compiled-in extensions
systemd (Linux, user unit) β ~/.config/systemd/user/php-fpm.service:
[Unit]
Description=PHP-FPM (managed by pvm)
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/share/pvm/versions/8.4.18/bin/php-fpm -y %h/.config/php-fpm/php-fpm.conf -F
Restart=on-failure
[Install]
WantedBy=default.targetsystemctl --user daemon-reload
systemctl --user enable --now php-fpm
journalctl --user -u php-fpm -fPin the full semver in ExecStart (e.g. 8.4.18) β symlinking to versions/8.4 is not maintained by pvm, so a future pvm install 8.4 that resolves to 8.4.19 will not move the service.
launchd (macOS) β ~/Library/LaunchAgents/dev.pvm.php-fpm.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>dev.pvm.php-fpm</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.local/share/pvm/versions/8.4.18/bin/php-fpm</string>
<string>-y</string>
<string>/Users/YOU/.config/php-fpm/php-fpm.conf</string>
<string>-F</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/php-fpm.out.log</string>
<key>StandardErrorPath</key><string>/tmp/php-fpm.err.log</string>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/dev.pvm.php-fpm.plistlocation ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}- The static binary is self-contained β no system libphp / no extension
.sofiles. Runphp-fpm -mto list the extensions baked into your build. - Switching the active CLI via
pvm use 8.3does not restart your fpm service; the service runs whichever absolute path you wired into the unit/plist. Bump the path and reload when you upgrade. - For multiple parallel versions (e.g. 8.3 + 8.4), run two services on different ports/sockets β
pvmdoes not multiplex fpm for you.