Numbers and method
What it costs, measured.
Marketing pages usually say "lightweight". Here is the arithmetic instead: a default install is two containers, 607 MB of images, about 113 MiB of memory, and it serves every project of your projects directory. Every figure below comes from a command you can run on your own machine.
The machine and the versions
| Machine | Apple M3, 8 cores, 16 GB, macOS 27.0.1 (arm64) |
|---|---|
| Docker | Docker 29.8.2 with Compose v2 (5.7.0), Linux containers, no VM of this project involved |
| Laser Stack | release 0.13.0 when measured; later releases change documentation, defaults and volume names, not the measured behaviour. Images and data volumes present — the state you are in from the second day |
| Measured | 2026-11-05, with docker images, docker stats --no-stream, time/date around laser commands, and curl |
The cost of a stack
laser up -d to the database answeringNumbers 1 and 2 come from the same stack: seventeen Laravel projects in the projects directory, one app container, one mariadb container.
Images, as docker reports them
Size on disk, arm64. The app image carries nginx, php-fpm, composer, node, npm, yarn, phpunit, xdebug and the mysql and postgres clients, so there is nothing else to install for a project.
| image | versions | size |
|---|---|---|
laserstack/app | 7.4 (272 MB), 8.0 (244 MB), 8.1 (327 MB), 8.2 (297 MB), 8.3 (302 MB), 8.4 (305 MB), 8.5 (314 MB), 8.6 (325 MB) | 244–327 MB |
mysql | 8.0 (795 MB), 8.4 (809 MB), 9.7 (954 MB), 26.10 (776 MB) | 776–954 MB |
mariadb | 10.11 (310 MB), 11.4 (355 MB), 11.8 (357 MB), 12.3 (361 MB), 13.0 (386 MB) | 310–386 MB |
postgres | 15.1 (241 MB), 16 (288 MB), 17 (291 MB), 18 (298 MB) — the -alpine series | 241–298 MB |
redis | 7.4 (39.5 MB), 8.2 (70.1 MB), 8.4 (95.9 MB), 8.6 (97.2 MB), 8.8 (115 MB), 8.10 (119 MB) | 40–119 MB |
memcached | 1.6-alpine (13.6 MB) | 13.6 MB |
elasticsearch | 7.14.2 (1.12 GB), 8.19.23 (992 MB), 9.5.5 (1.2 GB) | 992 MB–1.2 GB |
Two of these are worth a sentence: mariadb is the lighter SQL server (310 MB against 795 MB for mysql 8.0, both with the data directory empty), and elasticsearch is the one heavy profile — everything else in the stack fits in a few hundred megabytes.
Idle memory
docker stats --no-stream, after the stack was up and then left alone for 60 s:
| container | idle memory | note |
|---|---|---|
laserstack-app-1 (laserstack/app:8.2) | 35.5 MiB | nginx and php-fpm, one idle worker |
laserstack-mariadb-1 (mariadb:10.11) | 77.9 MiB | InnoDB pool allocated lazily |
laserstack-postgres-1 (postgres:15.1-alpine) | 16.2 MiB | right after start, no queries yet |
laserstack-redis-1 (redis:8.10-alpine) | 6.5 MiB | empty dataset |
laserstack-memcached-1 (memcached:1.6-alpine) | 1.3 MiB | empty cache |
mysql:8.0 | 375.3 MiB | measured alone, with the config directory of the repository mounted |
elasticsearch:7.14.2 | 6.4 GiB | 60 s after the HTTP API answered: the JVM plus the memory-mapped index files, as docker counts them |
So the default install — app and mariadb, serving every project — idles at about 113 MiB. Add redis and memcached for under 8 MiB more. MySQL 8.0 costs roughly five times what MariaDB costs in memory (375 MiB against 78 MiB, same configuration), which is worth knowing before you pick the mysql profile over mariadb. Elasticsearch is the exception in every stack, and here it is the only container that costs gigabytes: 6.4 GiB in docker stats, with the JVM and the memory-mapped index files counted against it, and tens of seconds of start-up.
Time to start
| what is measured | time | method |
|---|---|---|
laser up -d until the database answers a query | 1.0 s | mariadb profile, data volume present |
laser up -d until nginx and php-fpm are RUNNING | 2.9 s | app + mariadb, containers recreated by the command |
| the same, with mysql + redis + memcached | 2.8 s | four services up at once |
| the same, with postgres | 2.1 s | |
| the same, with elasticsearch | 3.6 s | the app container is up long before elasticsearch is: its API needs tens of seconds |
laser start of stopped containers | 3.7 s | no recreation, containers already exist |
These are warm numbers: the images are pulled and the data volumes exist. A first run also initializes the data directory of a database, which is where the extra seconds of a brand-new install go.
Serving projects
| what | figure | how |
|---|---|---|
| projects served by the same two containers | 17 | find "$PROJECTS_DIRECTORY" -maxdepth 2 -name artisan |
laser scan over those projects | 2.3 s | including hashing the vhosts to decide about an nginx restart |
| first response of a plain PHP project | 8.1 ms | curl from the host through nginx and php-fpm, 1-file project, first request |
| second response, same project | 5.6 ms | |
Laravel 12 /up route, first request after start | 492 ms | framework boot and opcode cache warm-up |
Laravel 12 /up route, steady state | 25–31 ms | four consecutive requests: 31.4, 26.1, 26.0, 25.3 ms |
What these numbers are not
- Not a comparison. A fair one compares whole workflows on the same machine: how much of your disk a VM takes, how long a per-project container set needs to come up, what a file-sync daemon costs in CPU. Those are other people's numbers and they are on their sites;
- Not cold starts. Pulling 600 MB of images takes as long as your connection does; a first database start also writes its data directory;
- One machine, one run. An M3 laptop with fast storage. Memory of a database grows with its workload, and
docker statsmemory includes page cache; - Not a promise about your project. 26 ms is a Laravel boot plus a health route. Your application's response time is your application's business.
Reproduce it
With a running stack, from the repository:
# images
docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | sort
# idle memory, after a minute of doing nothing
docker stats --no-stream --format '{{.Name}} {{.MemUsage}}'
# time from a stopped stack to a database that answers
laser stop
time ( laser up -d; until laser mysql -e 'select 1' >/dev/null 2>&1; do sleep 0.5; done )
# projects served, and how long the scan takes
find "$PROJECTS_DIRECTORY" -maxdepth 2 -name artisan | wc -l
time laser scan
# response time through nginx and php-fpm
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' -H "Host: myapp.$SUFFIX" http://127.0.0.1/
If your numbers differ, they are the ones that matter: this page exists so that nobody has to take a marketing claim on faith.