the localdev.it stack · macOS · Linux · WSL2 · docker-compose · one shell script · BSD-3-Clause
Many PHP projects.
One small stack.
Laser Stack serves all of your Laravel, Symfony and WordPress projects from a single set of Alpine containers — one nginx, one PHP-FPM, one database server — with the PHP and database versions chosen by a variable. No VM, no per-project containers, no dashboard application to install.
https on every project, by default — a real wildcard certificate your browser already trusts, so there is no CA to install, no warning page and no -k in your tests. How it works.
curl -fsSL https://localdev.it/laserstack/install | bash
Runs on macOS, Linux and WSL2/WSLg — Intel and Apple silicon, any docker with compose v2 (Docker Desktop, OrbStack, Colima, the engine). It installs nothing on the host: no VM, no system package, no daemon of its own.
https, out of the box
Every project answers on 443 as well as on 80, from a wildcard certificate a browser already trusts — no CA to install, no warning page, no browser exception.
Nothing to install, nothing to trust
No mkcert step, no CA in the keychain, no re-trust after an update. The certificate is a real one, it covers every name under the suffix — including the project you create tomorrow — and HTTPS=off is one line if you want plain http back.
A secure context, because it is not localhost
Service workers, Secure cookies, SameSite=None, navigator.clipboard, WebAuthn and camera or microphone permissions all require a secure context. projecta.localdev.it is not localhost, so over http they refuse — here they work, on 443, by default.
What your tools and your CI expect
curl, Postman, PHP, node and the browser validate the chain normally: no -k, no --insecure, no exception to remember. OAuth, SAML and payment callbacks that demand an https redirect URI work locally without a tunnel.
How https works here, and what a certificate of your own suffix takes →
$ curl -fsSL https://localdev.it/laserstack/install | bash
bash 5.3.20 git 2.56.0 docker 29.9.2 compose 5.7.0
system macOS, arm64
wrote .env from env.example in ~/src/laserstack
$ cd ~/src/laserstack && laser up -d
Container laserstack-app-1 Started
Container laserstack-db-1 Started
Container laserstack-redis-1 Started
$ laser scan
SCAN: /Users/you/src/projecta identified as laravel.
SCAN: /Users/you/src/projectb identified as wordpress.
$ laser ps
NAME IMAGE STATUS PORTS
laserstack-app-1 laserstack/app:8.4 Up 4 seconds 0.0.0.0:80->80/tcp
laserstack-db-1 mysql:8.0 Up 4 seconds 0.0.0.0:3306->3306/tcp
laserstack-redis-1 redis:8.10-alpine Up 4 seconds 0.0.0.0:6379->6379/tcp
$ # http://projecta.localdev.it and http://projectb.localdev.it are both served
What it costs
Measured on a MacBook (Docker 29, arm64), warm images and data volumes — the state you are in from the second day. The method is on the numbers page.
laser up -d to nginx and php-fpm servingBuilt for a small footprint
The whole stack is a compose file, a config directory and a shell script you can read in an afternoon.
Many projects, one stack
One nginx with a vhost per project, rendered by laser scan from the projects directory. Laravel, Symfony and WordPress drivers are included, and any project can ship its own nginx config in laserstack-nginx.conf.
Versions are variables
PHP_VERSION, MYSQL_VERSION, MARIADB_VERSION, POSTGRES_VERSION, REDIS_VERSION, MEMCACHED_VERSION, ELASTICSEARCH_VERSION. Each one selects the image tag and the matching config directory, so two installs can run different versions from the same repository.
Config lives in git
php.ini, php-fpm, supervisord, nginx and every database config are files of this repository, one directory per version. Change a value, restart one service, keep the change in a commit.
Alpine, no VM
The images are Alpine-based and pulled from public registries. There is no virtual machine, no custom orchestrator and no daemon: docker-compose does the work.
A CLI that stays out of the way
laser exposes every docker-compose command unchanged and adds the ones you type all day: artisan, composer, php, npm, shell, mysql, psql, share, scan.
Tested, not vibes
test/smoke.sh starts the stack once per PHP version and once per database profile and checks nginx, php-fpm, the php.ini of the repository, an HTTP response, an https response with a trusted certificate, and the PDO path from the app container to the server. The 0.13.0 release was verified against every version of every engine; 0.14.0 was verified on PHP 8.4 with mysql 8.0, mariadb 10.11 and postgres 15.1.
Always up to date
The app images are rebuilt every night, so laserstack/app:8.4 always carries the newest PHP 8.4 patch and a newer series appears as its own tag. laser update pulls the newest code and images and restarts only what changed.
Project names with no DNS setup
localdev.it is the default suffix, so every project answers as projecta.localdev.it: every name under that domain already resolves to 127.0.0.1 and ::1 for anyone. No hosts file, no dnsmasq, nothing to install.
A dashboard that describes the stack
The checkout is one of the projects, so the stack serves a read-only dashboard for itself on laserstack.<SUFFIX>: the PHP series and its config directory, every project with the driver laser scan found and the code its front page answered, the services, the database version, the certificate it serves with the days left, and the head of the changelog. It writes nothing and prints no credential. See what it shows.
No sshd, so nothing to log into
The containers run nginx and php-fpm under supervisord and nothing else: no sshd and no ssh client, in any image. That is why the OpenSSH-on-musl authentication bypass (CVE-2026-123158, fixed upstream in 10.6p2 on 2026-11-05) has no target here, and why the images are rebuilt every night from Alpine rather than patched by hand.
No magic, no lock-in
Nothing rewrites your compose file and nothing guesses. When a container is missing you get docker's own error, which is the error you can act on.
macOS, Linux, WSL2
Official support for macOS (Intel and Apple silicon), Linux and WSL2/WSLg, with Docker Desktop, OrbStack, Colima or the docker engine. The projects directory is bind-mounted, so your editor keeps working on your own files.
Quick start
Three commands and a hosts entry. The clone belongs next to your projects, because that directory is what the stack mounts and serves.
Clone it next to your projects
git clone https://github.com/sdbruder/laserstack ~/src/laserstack
alias laser=~/src/laserstack/laser # or add it to $PATHWrite your .env
cd ~/src/laserstack && cp env.example .env
# PHP_VERSION=8.4
# PROFILES=mysql,redis
# DB_PASSWORD=… MYSQL_VERSION=8.0 REDIS_VERSION=8.10
# SUFFIX=localdev.it # the default, no setup: projecta.localdev.itStart it and scan
laser up -d
laser scan # writes one nginx vhost per project
# nothing else: http://projecta.localdev.it and http://projectb.localdev.it answer
# with a suffix of your own (SUFFIX=test): 127.0.0.1 projecta.test projectb.testDay to day: laser artisan migrate, laser npm run dev, laser mysql, laser shell, laser share, laser stop. Anything else goes straight to docker-compose: laser logs app --tail 50.
What it does not do (yet)
The stack is opinionated and honest about the edges. Everything below is on the roadmap or documented as a limit.
One PHP version at a time
All projects of one stack share the app container, so they share its PHP version. Per-project PHP versions, one container per version in use, are the next feature.
One version per database engine
You can run mysql and postgres and redis side by side, but each engine runs the version you selected. Several versions of the same engine in parallel is part of the same roadmap item.
A suffix of your own needs /etc/hosts
The default suffix, localdev.it, resolves every name under it to loopback for anyone and needs nothing. A local dnsmasq that laser installs, configures and maintains on macOS, Linux and WSL2 is planned for a suffix of your own.
No GUI, no native Windows
The CLI is the interface, and Windows runs through WSL2; macOS and Linux run as they are. Per-project PHP versions are planned, a desktop app is not.
Where to go next
Features in detail →
What the drivers, the profiles, the versions and the CLI actually do.
How it compares →
Valet, Herd, Sail, DDEV, Lando and Laradock, on the axis that decides the bill: one stack for all projects, or one per project.
Numbers and method →
Image sizes, idle memory, startup time, and how each figure was produced.
Documentation →
The .env reference, the profiles, the versions and the laser commands, with links to the repository docs.
Examples →
Copy-paste recipes: many projects, a custom vhost, another database, another database version, artisan and npm.