A local development environment for Windows — PHP, Caddy, PostgreSQL, MariaDB, Node, Redis — that installs beside the system rather than into it.
Status: 1.4. PHP, Caddy, PostgreSQL, MariaDB, Redis and Node install and run; sites are served at
*.localhost, over HTTP and HTTPS; several versions of each run side by side. There is a full-screen dashboard, it replaces itself withupgrade, andservice cliopens a prompt at a running database. In daily use on real Windows — see the note at the end for what that has and has not covered.
The two version badges say different things: tag is what was cut, release is what CI built and published. They agree unless a release failed, which is worth seeing from here rather than finding out at a download.
Documentation: English · Русский · Deutsch · 中文
Laragon solves this problem on Windows and solves it well. This exists because its distribution terms changed, and because a tool you depend on daily is worth being able to read, fork and keep.
macOS and Linux are not the target: there you can assemble the same stack by hand in an afternoon, and Homebrew or apt will keep it fed. On Windows you cannot, which is why every tool in this category exists there.
Every one of these is a design constraint, not an aspiration:
- No administrator rights. Not at install, not at runtime, not ever.
- No
hostsfile. Sites are reached at*.localhost, which Windows and macOS both resolve to the loopback on their own — verified on both, in the browser and from the shell. - No services, no autostart. The supervisor is a process you start. It survives closing the terminal and the IDE; it does not survive a reboot, and that is the trade being made deliberately.
- No registry, no PATH, no system directories. Everything lives under one
directory. Deleting it uninstalls the tool completely — Caddy's certificate
authority and keys included, which takes telling Caddy: its own default is to
keep them under
%AppData%. - HTTPS without elevation. The local authority's root goes into the user's
trust store, which needs no administrator. Caddy would install it itself, into
the machine's, warning that it "might prompt for password" — that is switched
off, and
portable trustis a separate, deliberate act. - That directory is yours to choose.
%LOCALAPPDATA%\portableis only the default — see below.
The result runs on a locked-down corporate machine, which is precisely where this class of tool usually cannot be installed at all.
The CLI is a client, not the program. A supervisor daemon owns the runtimes
and the processes and exposes a token-authenticated control API on the loopback;
portable is the first client of that API. An IDE plugin, when it comes, will
be the second — with nothing to retrofit, because there is no functionality
reachable only from the command line.
Runtimes come from their publishers, verified. Versions resolve against the
publisher's own index — php.net's releases.json, Caddy's GitHub releases — and
archives are checked against the digests those publishers list. A mismatch
deletes the file: everything downloaded here is executed afterwards.
Both downloaded and discovered runtimes are managed. A PHP the tool installed and a PHP already on the machine are the same kind of thing to the supervisor. That is not a fallback — it is how anyone needing an extension the prebuilt binaries lack stays unblocked.
There is no php-fpm on Windows. FPM is a Unix-only SAPI; the Windows build
ships php-cgi.exe and nothing else. One process serves one request at a time,
and PHP_FCGI_CHILDREN needs fork(). So the supervisor runs a pool of
php-cgi.exe processes on separate ports and the router balances across them.
Process supervision is therefore the core of this tool, not plumbing around it.
nginx disclaims its own Windows build. From nginx's documentation: it uses
"only the select() and poll() connection processing methods, so high
performance and scalability should not be expected", and is "considered to be a
beta version". Caddy is used instead — a maintained native binary, with an admin
API that makes adding a site one HTTP call, and a local CA that solves HTTPS
without touching the machine's trust store.
In PowerShell, no administrator rights needed:
irm https://github.com/ghraw/dskripchenko/portable/main/install.ps1 | iexPiped into iex rather than saved and run, because under the Restricted
execution policy — the default, and the setting most often enforced on a managed
machine — a .ps1 file on disk will not run while a string does.
Where PowerShell itself is locked down — AppLocker and WDAC put it into
Constrained Language Mode, in which even Get-FileHash refuses to run — nothing
above is required. From cmd, executing no script at all:
curl -fsSL -o portable.zip https://github.com/dskripchenko/portable/releases/latest/download/portable-windows-x64.zip
certutil -hashfile portable.zip SHA256
tar -xf portable.zipOr download the bundle from the releases, unzip it anywhere, and run the launcher beside it. There is nothing to install — the interpreter ships with the tool, because a program that installs runtimes on a machine which has none cannot sensibly require one first.
portable up # start the daemon
portable available php # what the publisher currently offers
portable available php 8.3 # including superseded patches, from the archive
portable install php # or: --from C:\your\own\php
portable install caddy
portable site add demo C:\projects\demo
# -> http://demo.localhost
# serves public/ if the front controller
# is there; --exact to take the path as given
portable port 8888 # when 80 and 8080 are both taken
portable trust # -> https://demo.localhost works
portable update # newer releases on the same line
portable update --install
portable uninstall php 8.3.20 # reclaim the disk afterwards
portable ext list # what this PHP ships, and what is loaded
portable ext enable sodium
portable ext install xdebug # not in the build — downloaded to match it
portable service add postgres # 127.0.0.1:5432, user postgres
portable service add redis
portable install node
portable run npm install # with the installed runtimes reachable
portable status
portable down
portable dash # everything at once, full screen
portable logs -f # what everything is saying, live
portable shell # commands without retyping `portable`
portable help # every command, with worked examples
portable version # this, the interpreter, the running daemon
Every command takes --json. The CLI holds no logic of its own: it asks the
daemon and prints the answer, which is why an IDE plugin will be a second client
rather than a second implementation.
portable home # where, and what decided that
portable home set D:\dev\portable # somewhere else, from now on
portable home set --beside # next to the launcher; travels with it
portable home clear # back to the default
portable --home E:\tmp status # just this once
The default is %LOCALAPPDATA%\portable, and on a managed machine that default
can be unusable rather than merely unwelcome: AppLocker is commonly configured
to deny execution from under a user's profile — that is where software installed
without administrator rights lives, which is the point of the rule — and
everything downloaded here is an executable. Where that applies, nothing starts
until this is pointed somewhere execution is allowed.
--beside is the case an absolute path cannot express. It records the word, not
the path it resolves to today, so a bundle on a flash drive keeps working when
the drive letter changes.
Changing this moves nothing. Copying hundreds of megabytes of runtimes would be a surprising thing for a settings command to do, and a copy that failed halfway would leave two half-installations — so the old location is reported instead, with what is still in it.
python -m pytest
python scripts/bundle.py --target x86_64-pc-windows-msvcThe tests run on any platform. Anything Windows-specific — process detachment,
the php-cgi pool — is exercised on CI, because the parts of this that cannot be
tested from a developer's Mac are exactly the parts most likely to be wrong.
Fixtures are captured from the publishers rather than written by hand. A hand-made fixture only proves the parser agrees with its author: Caddy publishes sha512 checksums in a file that looks exactly like a sha256 listing, and only a real one catches that.
Everything here runs, and most of it has been run against the real thing: a
php-cgi pool behind Caddy serving PHP, PostgreSQL initialised and queried,
Node reached through portable run, and the bundle started on a machine with
its PATH pointing at nothing.
Most of that was on macOS, which shares php-cgi, the FastCGI protocol and the
archive formats with the target but not everything else.
Confirmed on real Windows: php-cgi.exe serving a Laravel project through
the pool; port 80 bound by an ordinary user, with no administrator rights —
the premise the whole design rests on; runtimes downloading and unpacking over a
network that resets TLS connections mid-handshake; installing under a PowerShell
locked into Constrained Language Mode; detaching from the console and the
process group; the dashboard; and logs and shell.
upgrade was on that list and should not have been. It passed its tests,
including on windows-latest, and on a real machine it downloaded, verified,
and left the old version in place every time. It renamed the bundle directory,
and Windows will not rename a directory that is any process's current directory
— which it is, twice over, when you run the upgrade from the folder the
documentation tells you to stand in. Nothing in the test suite stood anywhere.
Fixed in 1.3.2 by moving the contents instead, with a test that holds the
directory the way a terminal does.
One limitation, measured rather than promised. A process can leave a job
object only if that job permits it, and some launchers create one that does not.
Against such a job nothing at the process level escapes, so the supervisor dies
when the launcher closes — and portable up says when it is in one, rather than
leaving that to be found out later. Both cases are tested on windows-latest on
every run.
macOS is not a target. Every catalog resolves Windows archives and nothing else, so the tool runs there but installs binaries that machine cannot execute. A macOS bundle is built on CI as a check that the bundler still works, and is deliberately not published.
MIT.