A configurable, batteries-included Docker dev environment for Magento 2 (Open Source / Adobe Commerce / Cloud) and MageOS — built around the daily ergonomics of a working Magento developer.
I've been running Magento 2 stores for years and, over time, my local Docker setup grew into something I genuinely enjoy using every day. xdebug just works. n98-magerun2, flushfront, dbimport, magento aliases — all there from the moment a container boots. Bash history persists. Clearing Redis is one command. Switching projects doesn't mean fighting subnet conflicts. None of it is magic — it's just an accumulation of small choices that add up to "I never have to think about my dev environment, I just work."
A few colleagues started borrowing my Docker setup and kept telling me it was noticeably faster and easier than the alternatives they'd tried. At some point it felt selfish to keep it private, so here it is — cleaned up, made configurable, and tested across macOS and Linux.
If you want a Magento dev stack you can spin up in five minutes, that gets out of your way for the rest of the day, this is for you. Take it, use it, fork it, send PRs.
This stack runs any flavor of Magento 2 that shares the same codebase: Magento Open Source, Adobe Commerce on-premise, Adobe Commerce Cloud, and MageOS. Behind the scenes, it's all the same PHP application with different module sets layered on top.
A few practical notes:
- Magento Open Source / MageOS — works out of the box, no credentials required (use the MageOS repository for the credential-free path). MageOS publishes its own compatibility matrix at mage-os.org/get-started/system-requirements/; the entries under
MAGEOS_VERSIONSindockerimages/bin/init.shtrack it (MageOS 2.3.0 ≡ Magento 2.4.8-p5; 2.2.x ≡ 2.4.8; 2.0 / 2.1 ≡ 2.4.8-p3). - Adobe Commerce on-premise — works the same, you just need your Marketplace access keys when running
composer create-projectfromrepo.magento.com. - Adobe Commerce Cloud — the application code runs identically. What this stack does not do is simulate the Cloud-specific deployment lifecycle (
ece-tools,magento-cloud-patches, post-deploy hooks). For day-to-day development on Cloud projects — writing modules, running tests, debugging — this stack is fine. For testing actual Cloud deployments, use Adobe's officialmagento-cloud-docker.
The compatibility matrix in dockerimages/bin/init.sh enforces the right combinations of PHP / database / OpenSearch for each Magento patch release; you can't accidentally pick something Adobe doesn't support.
- PHP-FPM with Composer 2, n98-magerun2, Xdebug 3 (zero-config), MailHog handler, ImageMagick, full Magento aliases (
magento,n98,flushfront,dbimport, …) and a seeded.bash_historyso the shell remembers what you usually do. - Nginx with self-signed SSL for your local domain, automatic switching between “direct” and “Varnish-fronted” vhosts.
- MariaDB or MySQL — your choice, with version compatibility matched to the Magento release you picked.
- OpenSearch — image automatically matched to the Magento version.
- Redis 7, MailHog, phpMyAdmin — always on.
- Varnish 7 — optional, fully wired with a Magento 2 VCL.
- Node.js 22 — optional, Vite HMR port exposed.
The stack is designed so the things you do ten times a day take one keystroke and the things you do once never trip you up.
- RabbitMQ — listed as optional in the MageOS system requirements and only needed for high-traffic async processing (Adobe Commerce message queues). Skipping it keeps the dev stack lean. If you need it for testing, add a
rabbitmqservice block tocompose.yamlafter it's been generated — but remember the file is regenerated bymake rebuild-config, so for permanent additions you'd patchdockerimages/bin/render-compose.shinstead. - Elasticsearch — superseded by OpenSearch in Magento 2.4.6+. The stack ships OpenSearch only.
- Docker Engine 24+ with the Compose v2 plugin (
docker compose …). - GNU Make.
- About 6 GB RAM available to Docker on macOS / Windows.
git clone https://github.com/magentofullstackdev/magento-docker-bootstrap.git myproject
cd myproject
make configure # interactive — answer the questions
make init # build with --no-cache and start (first run)
make sethostip # write the container IP into /etc/hosts (sudo)Open https://<your-domain> and accept the self-signed cert.
For all subsequent starts use make up (or make start if images are already built). make init is reserved for the first build because it forces --no-cache.
Once the stack is configured, you have three ways to get a Magento codebase into httpdocs/:
| Goal | Command | Prerequisite |
|---|---|---|
| Fresh Magento / MageOS install | make install |
httpdocs/ contains composer.json (e.g. after composer create-project) |
| Existing project, no DB | make composer-install |
code already in httpdocs/, no DB needed |
| Existing project + DB dump | make import-db [FILE=path] |
a .sql.gz dump in db_dumps/ |
Important: Magento's project root (the folder with
composer.json,bin/,app/,pub/) must sit directly insidehttpdocs/, not in a subfolder.
For step-by-step walkthroughs of the three common scenarios — fresh Magento install, fresh MageOS install, or cloning an existing project from Git — see docs/INSTALL.md.
Run make help to see the same list grouped and coloured.
| Command | What it does |
|---|---|
make configure |
Interactive bootstrap — writes .env and renders compose.yaml. |
make configure FILE=path/to/answers.env |
Non-interactive — reads all answers from a file. Useful for CI / scripted setups. See tests/fixtures/ for examples. |
make rebuild-config |
Re-render compose.yaml from current .env without re-asking questions. |
make check |
Verify .env and compose.yaml exist. |
make ensure-volumes |
Create the external magento-composer-cache volume (idempotent). Auto-called by init / up / start / rebuild. |
make test |
Run fast smoke tests (no docker run). |
make test-full |
Run full smoke tests (builds images, brings stack up). |
| Command | What it does |
|---|---|
make init |
build --no-cache + up -d. Use this on the first run. |
make up |
Start (build if needed). Day-to-day power-on. |
make start |
Start already-built containers (no build step). |
make stop |
Stop containers, keep data. |
make restart |
Restart all containers. |
make kill |
Stop and remove containers and volumes. Destroys the DB. |
make rebuild |
kill + build --no-cache + up. Full reset. Destroys the DB. |
make logs |
Tail all logs. |
make ps |
Container status. |
| Command | What it does |
|---|---|
make shell |
bash inside php-fpm as www-data (default user). |
make shell-root |
bash inside php-fpm as root — for fast apt install etc. |
make redis |
bash inside the redis container. |
make db |
bash inside the database container. |
| Command | What it does |
|---|---|
make myip |
Print the nginx container's IP. |
make sethostip |
Append <web-IP> $SITE_HOST to /etc/hosts (sudo). Re-runs replace the existing line. |
make setdomain DOMAIN=foo.local |
Update SITE_HOST in .env and re-render compose.yaml. |
make subnets |
List Docker networks with their subnets — handy when debugging subnet conflicts. |
| Command | What it does |
|---|---|
make install |
Fresh bin/magento setup:install using .env values. Idempotent — uses a flag file. |
make import-db |
Import db_dumps/latest_dbdump.sql.gz, run setup:upgrade, flush caches. |
make composer-install |
composer install inside php-fpm. |
| Command | What it does |
|---|---|
make cache-flush |
bin/magento cache:flush |
make reindex |
bin/magento indexer:reindex |
make compile |
setup:upgrade + setup:di:compile |
make static-deploy |
setup:static-content:deploy -f |
make db-export |
Dump DB into db_dumps/dump-YYYYMMDD-HHMM.sql.gz |
make db-cli |
MySQL/MariaDB CLI inside the db container |
make redis-flush |
FLUSHALL |
make clean-all |
kill + remove .env, compose.yaml, install flag |
| Command | What it does |
|---|---|
make xdebug-on |
Enable Xdebug at runtime — re-registers the zend_extension and restarts php-fpm. |
make xdebug-off |
Disable Xdebug at runtime — unloads the zend_extension (no engine overhead) and restarts php-fpm. |
make xdebug-status |
Print Xdebug: ON / Xdebug: OFF based on the running container. |
State does not persist across container recreation: make rebuild and make kill && make up boot the stack with Xdebug enabled (the image bakes it in at build time). If you want Xdebug off by default, remove the docker-php-ext-enable xdebug line in dockerimages/config/php-fpm/Dockerfile and rebuild.
This was the macOS pain point in my original setup. The initializer detects your OS and behaves differently:
On Linux, each service gets a fixed IP in a 10.10.X.0/24 subnet. The third octet is auto-detected at make configure time: the bootstrap scans existing Docker networks and host routes, picks the first free /24 from the candidate list 10.10.{5, 10, 15, …, 250} (step 5, 50 slots), and writes the chosen value into .env as DOCKER_SUBNET_BASE. Only the third octet varies between projects — the last octet stays the same so muscle memory carries over:
| Service | Last octet | Example (DOCKER_SUBNET_BASE=110) |
|---|---|---|
| db | .2 |
10.10.110.2 |
| redis | .3 |
10.10.110.3 |
| web (nginx) | .4 |
10.10.110.4 |
| php-fpm | .5 |
10.10.110.5 |
| opensearch | .6 |
10.10.110.6 |
| nodejs | .7 |
10.10.110.7 |
| mailhog | .8 |
10.10.110.8 |
| phpmyadmin | .9 |
10.10.110.9 |
| varnish | .10 |
10.10.110.10 |
The php-fpm container's extra_hosts automatically points the local domain at the ingress IP (Varnish if enabled, otherwise nginx) so cron / CLI requests resolve correctly. make sethostip reads that IP and writes it into your host's /etc/hosts.
If you ever hit a subnet conflict later (a VPN comes up, you spin up another project), edit DOCKER_SUBNET_BASE in .env and run make rebuild-config && make rebuild. Use make subnets to see what's already taken.
On macOS / Windows, the static-IP block is dropped entirely — Docker Desktop ignores ipv4_address on user-defined bridges. Containers find each other via Docker's built-in service-name DNS (db, redis, php-fpm, …), and extra_hosts uses host.docker.internal + host-gateway. make sethostip still works: it asks the running container for its real IP via hostname -i and writes that into /etc/hosts.
So make sethostip is the right command on every OS — the implementation just adapts.
Enabled in trigger mode on port 9001 (mapped to host). To start a session:
- PhpStorm: enable “Listen for PHP Debug Connections”, set the language level to your chosen PHP version, mark
httpdocs/as the deployment root, and add a path mappinghttpdocs/->/var/www/html. The IDE server name configured in the container is${SITE_NAME}Docker(your project name, capitalised — e.g.HyperionDocker). - Browser: use a Xdebug helper extension and toggle it on.
xdebug.discover_client_host=true means you do not need to set a client IP — Xdebug talks back to whoever opened the connection.
Xdebug is loaded by default, which adds noticeable overhead on every request even when no debug session is active. To turn it off without rebuilding the image:
make xdebug-off # unloads the extension, php-fpm restart (~1s)
make xdebug-on # reloads it
make xdebug-status # ON / OFFToggling unloads the zend_extension itself (not just xdebug.mode=off), so there is zero engine instrumentation overhead when off. The state resets to ON after make rebuild or any container recreation.
| Service | URL / port |
|---|---|
| Storefront | https://${SITE_HOST} |
| Admin | https://${SITE_HOST}/admin |
| MailHog UI | http://localhost:8025 |
| phpMyAdmin | http://localhost:8080 |
| Varnish (if enabled) | http://localhost:8081 |
| Vite HMR (if Node enabled) | http://localhost:5173 |
| MySQL/MariaDB host port | 3300 |
| Xdebug | 9001 |
The initializer asks for:
- Project name (used as the Docker network, volume prefix, container prefix).
- Local domain (e.g.
myproject.local). - Platform: Magento 2 (Open Source / Adobe Commerce) vs MageOS.
- Version (sorted newest-first).
- PHP version (only the versions compatible with the chosen Magento release).
- Database engine and version.
- Whether to enable Varnish.
- Whether to enable Node.js.
It writes .env and renders compose.yaml from a fragment template. To regenerate compose.yaml after editing .env by hand, run make rebuild-config (no questions). To start over, make clean-all.
.
├── Makefile
├── compose.yaml # generated by `make configure`
├── .env # generated by `make configure`
├── httpdocs/ # your Magento codebase lives here
├── db_dumps/ # drop your DB dumps here
└── dockerimages/
├── bin/
│ ├── init.sh # interactive bootstrap
│ └── render-compose.sh # idempotent compose renderer
└── config/
├── database/ # MariaDB / MySQL tuning
├── nginx/ # nginx + 2 vhost templates (direct / varnish)
├── nodejs/ # Node 22 image
├── php-fpm/ # parametrized PHP-FPM image (PHP_VERSION ARG)
│ ├── bin/ # install.sh, user-sudoers
│ ├── config/ # php-fpm.ini, php-fpm.conf, xdebug.ini
│ └── users/ # .bashrc, .bash_history (mounted live)
└── varnish/ # default.vcl
The initializer enforces these combinations (extend the maps at the top of dockerimages/bin/init.sh to add more):
| Magento | PHP | DB | OpenSearch | Redis | Varnish |
|---|---|---|---|---|---|
| 2.4.6 | 8.1, 8.2 | MariaDB 10.4/10.6, MySQL 8.0 | 2.5, 2.12 | 7.0 | 7.1 |
| 2.4.7 | 8.2, 8.3 | MariaDB 10.6/10.11, MySQL 8.0 | 2.12, 2.19 | 7.2 | 7.4 |
| 2.4.8 | 8.3, 8.4 | MariaDB 10.6/11.4, MySQL 8.0/8.4 | 2.12, 2.19, 3.0 | 7.4 | 7.6 |
| 2.4.9 | 8.4, 8.5 | MariaDB 11.4, MySQL 8.4 | 2.19, 3.0 | 7.4 | 7.7 |
| MageOS 2.3.0 (≡ Magento 2.4.8-p5) | 8.2, 8.3, 8.4 | MariaDB 10.6/10.11/11.4, MySQL 8.0/8.4 | 2.12, 2.19, 3.0 | 7.4 | 7.7 |
| MageOS 2.2.x (≡ Magento 2.4.8) | 8.2, 8.3, 8.4 | MariaDB 10.6/10.11/11.4, MySQL 8.0/8.4 | 2.12, 2.19, 3.0 | 7.4 | 7.7 |
| MageOS 2.0 / 2.1 (≡ Magento 2.4.8-p3) | 8.2, 8.3, 8.4 | MariaDB 10.6/10.11/11.4, MySQL 8.0/8.4 | 2.12, 2.19 | 7.4 | 7.6 |
Redis and Varnish image tags are auto-selected by the configurator from this matrix — you don't get a prompt for them, because picking a mismatched version against a given Magento patch is the kind of subtle breakage we want to make impossible. The selected tags land in .env as REDIS_VERSION / VARNISH_VERSION; override there if your project deliberately runs a different version.
2.4.9 dropped a lot of legacy versions. PHP 8.3 is upgrade-only (not allowed for fresh installs), MySQL 8.0 and MariaDB 10.6 are gone, and OpenSearch 3.x is the officially supported search engine. OpenSearch 2.19 is kept here as a migration path for projects coming from 2.4.8.
Always confirm against the Adobe system-requirements page for the exact patch release before going live with these picks. OpenSearch images come from the official opensearchproject/opensearch registry on Docker Hub (not Adobe's magento-cloud-docker-opensearch, which has historically been irregular about publishing tags for newer OpenSearch releases). Required Magento plugins (analysis-icu, analysis-phonetic) are installed automatically on first start via the OPENSEARCH_PLUGINS env var.
To see what OpenSearch versions are currently available on Docker Hub before extending the matrix, run make check-images.
Pull requests are welcome — see CONTRIBUTING.md. The most useful contributions are usually new entries in the compatibility matrix as Magento patch releases land, and any sharp-edge fixes you hit on a host OS I haven't tested on.
MIT — do whatever you want with it, just don't blame me if your laptop catches fire.
Author: Sergiu Ro. — magentofullstack.dev · sergiu.ro@magentofullstack.dev
If this saves you a few hours of yak-shaving, drop me a line. Always happy to hear how other people use it.