Documentation
Everything you need to run it.
This page is the practical reference. The repository holds the deep documentation: the README for users and CONTRIBUTING.md for the internals, the tests and the release process.
Requirements
- Docker with Compose v2 (
docker composeordocker-compose). That is the only heavy dependency; - git and bash on the host. No PHP, no Node, no database client, no package from a language ecosystem — the containers carry those;
- macOS (Intel or Apple silicon), Linux, or Windows through WSL2/WSLg — on WSL keep the projects inside the Linux filesystem (
~/src), not under/mnt/c, and the bind mounts stay fast; - a directory where your projects live (this is what the stack mounts and serves), and the stack cloned next to it.
Install
curl -fsSL https://localdev.it/laserstack/install | bash
# or clone it yourself; the installer only does these steps for you:
git clone https://github.com/sdbruder/laserstack ~/src/laserstack
cd ~/src/laserstack
cp env.example .env # laser also copies it on first use
# edit .env: PROJECTS_DIRECTORY, PHP_VERSION, PROFILES, credentials, SUFFIX
laser up -d
laser scan
laser dashboard # the stack in your browser
laser help # the commands; laser dockerhelp for the compose ones
Make laser reachable from your projects: an alias or a $PATH entry to the cloned directory. laser always works from its own directory, so a project can call laser artisan migrate from anywhere.
The .env reference
| variable | default in env.example | what it does |
|---|---|---|
PROJECTS_DIRECTORY | ./.. | the directory bind-mounted into the app container at /var/www and scanned for projects |
SUFFIX | localdev.it | the domain suffix of a project URL: projecta.localdev.it. Every name under localdev.it resolves to 127.0.0.1 and ::1 for anyone, so the default needs no setup at all; a suffix of your own, test for instance, needs the names in /etc/hosts |
PHP_VERSION | 8.4 | the app image series, laserstack/app:<version>: 7.4, 8.0–8.6 |
MYSQL_VERSION | 8.0 | the mysql profile image and its config directory |
MARIADB_VERSION | 10.11 | the mariadb profile image and its config file |
POSTGRES_VERSION | 15.1 | the postgres profile image (-alpine is appended) and its config directory |
REDIS_VERSION | 8.10 | the redis profile image and redis.conf |
MEMCACHED_VERSION | 1.6 | the memcached profile image |
ELASTICSEARCH_VERSION | 7.14.2 | the elasticsearch profile image and elasticsearch.yml |
DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_ROOT_PASSWORD | laser, laser, laser, root | the database and the accounts the mysql profile creates |
MARIADB_* | same shape | the same for the mariadb profile: database, user, password, root password |
POSTGRES_PASSWORD | postgres | the postgres password. POSTGRES_USER, POSTGRES_DB, POSTGRES_INITDB_ARGS, POSTGRES_HOST_AUTH_METHOD are also read when set |
PROFILES | mysql,redis | the additional containers to run, comma separated: mysql, mariadb, postgres, redis, memcached, elasticsearch |
The versions each engine supports are on the features page. A variable that names a version without a config directory gives you docker's own "bind source path does not exist" error: there is no validation layer in between.
Which container answers to which name
Inside the docker network, a project talks to the services by name; from the host, by the published port:
| service | name inside the network | host port |
|---|---|---|
| mysql | db | 3306 |
| mariadb | mariadb and the alias db | 3306 |
| postgres | postgres | 5432 |
| redis | redis | 6379 |
| memcached | memcached | 11211 |
| elasticsearch | elasticsearch | 9200 and 9300 |
Because the mariadb service carries the alias db, a project whose .env says DB_HOST=db works with the mysql profile and with the mariadb profile. Run one of them at a time: they both want port 3306.
Serving your projects
- Every subdirectory of
PROJECTS_DIRECTORYwith apublic/directory is a candidate site;laser scandecides what each one is and writes its vhost; - the drivers are laravel, symfony, wordpress and generic. The generic driver installs a
laserstack-nginx.conffrom the project root as the vhost of that project; - the name a request must use is
<directory>.<SUFFIX>, soprojecta.localdev.itfor a directory calledprojectawith the default suffix; - those names must resolve to 127.0.0.1, and the default suffix needs nothing:
env.examplesetsSUFFIX=localdev.it. Every name underlocaldev.italready resolves to127.0.0.1(and::1) for anyone, soprojecta.localdev.itworks on this machine the moment the stack is up — no hosts file, no DNS server, nothing to install. The notes behind that domain are at localdev.it/catch-all-dns.html; - with a suffix of your own, add the names to
/etc/hostsor point a local DNS at them. Installing and maintaining that DNS is on the roadmap; - projects live at
/var/www/<directory>in the app container, so a project's own paths, relative URLs and artisan invocations stay ordinary; - after adding or removing a project:
laser scan. It restarts nginx only when the vhosts really changed; - after changing
SUFFIX:laser up -dfirst, so the containers read the new value, and thenlaser scanfor the vhosts. Until the containers are recreated the stack keeps answering on the old suffix.
Working inside a project
Run these from the project directory; the current directory decides which project the command applies to:
cd ~/src/myapp
laser artisan migrate
laser composer require laravel/sanctum
laser npm run dev
laser test # php artisan test
laser tinker
laser shell # bash in the app container, in this project
# from the repository root, laser appexec talks to the container itself:
laser appexec supervisorctl status
The images carry composer, node, npm, yarn, phpunit, xdebug and the mysql and postgres clients, so the tool version of a project is the tool version of its PHP series — the same for everyone on the team.
Databases
laser mysqlandlaser psqlopen the client of the database container of the active profile, with the credentials of.env, and pass extra arguments through:laser mysql -e "select version()",laser psql -c '\dt';- from the host, connect to
127.0.0.1on the published port (3306, 5432, 6379, 11211, 9200) with the credentials in.env— for a GUI client, a dump/restore or a script; - a project reaches its database by name:
DB_HOST=dbfor mysql and mariadb,DB_HOST=postgres,REDIS_HOST=redis. Those host names are whatlaser scanand the drivers assume; - the elasticsearch profile runs with security disabled (
xpack.security.enabled: false) so a local application can talk to it without certificates; - each database version has its own data volume (
<project>_dbdata_<version>, and the same shape for mariadb and postgres), so changing a version keeps every other version's data and needs nolaser down -v. A data directory is still readable only by the server version that created it, which is why the version is part of the volume name; - the app image's
mysqlclient is built on MariaDB Connector/C and cannot authenticate against MySQL 8.4 and newer, whose accounts usecaching_sha2_password. Uselaser mysql, which runs the client of the database container.
Testing your setup
test/smoke.sh starts the stack for real and checks it: nginx and php-fpm, the php.ini of the repository, an HTTP response, and the PDO path from the app container to the database. It runs once per PHP version and once per database profile, and a version variable drives it:
test/smoke.sh # every version, every profile
test/smoke.sh 8.4 8.5 # only these PHP versions
SMOKE_DB_PROFILES=mariadb test/smoke.sh 8.2 # one database profile
SMOKE_MYSQL_VERSION=9.7 SMOKE_DB_PROFILES=mysql test/smoke.sh 8.5
SMOKE_PULL=0 test/smoke.sh 8.4 # use the local images
The test stops the stack at the end and leaves .env as it was. It is also the fastest way to find out whether a problem is yours or the stack's.
Staying up to date
The images of the PHP series are rebuilt every night in laserstack_app, so a series tag such as laserstack/app:8.4 carries the newest 8.4 patch available. Taking what is new is one command:
laser update --check # fetch the code and the images, change nothing
laser update # the same, then restart the containers whose image changed
- it fast-forwards the checkout first (
git pull --ff-only, when the directory is a git working tree). An edit of yours to a tracked file stops that pull and it says so, instead of merging behind your back;.envand untracked files are never touched; - then it pulls every image of the active profiles, the app image included, and prints the PHP version the image carries next to the one the container is running;
- then it brings the stack up, which recreates exactly the containers whose image changed, and prints the version that is now running;
- a new PHP series is the one manual step: a version directory in laserstack_app plus
config/php<VV>andconfig/supervisord.<VV>here.
Known limits
- one PHP version per stack. Every project of a stack shares the app container. Per-project PHP versions are planned;
- one version per database engine. mysql, mariadb, postgres, redis, memcached and elasticsearch can run side by side, but each engine runs the version you selected;
- a suffix of your own needs
/etc/hosts. The default suffix,localdev.it, resolves every name under it to loopback for anyone, so it needs nothing at all; a local dnsmasq thatlaserinstalls and maintains, for a suffix of your own, is planned; - memcached is a cache store from 7.4 to 8.5: the images carry the
memcachedextension there. PHP 8.6 is a release candidate and has neither an Alpine package nor a pecl build of the extension yet, so that series has the server without the client; - no Windows-native support: use WSL2. macOS and Linux run as they are;
- no environment validation: by design. A missing image, an unknown config path or a stopped container gives you docker's own error.
Read more
- README.md — the user documentation in the repository;
- CONTRIBUTING.md — how the stack is put together, how to add a version, how to run the tests, how to release;
- CHANGELOG.md — what changed in every release, newest first;
laser helpandlaser dockerhelp— the command list, from the script itself.