Skip to content

magentofullstackdev/magento-docker-bootstrap

Repository files navigation

Magento / MageOS Docker Bootstrap

smoke

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.

Why this exists

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.

Supported flavors

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_VERSIONS in dockerimages/bin/init.sh track 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-project from repo.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 official magento-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.

What you get

  • 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_history so 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.

What's intentionally missing

  • 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 rabbitmq service block to compose.yaml after it's been generated — but remember the file is regenerated by make rebuild-config, so for permanent additions you'd patch dockerimages/bin/render-compose.sh instead.
  • Elasticsearch — superseded by OpenSearch in Magento 2.4.6+. The stack ships OpenSearch only.

Prerequisites

  • Docker Engine 24+ with the Compose v2 plugin (docker compose …).
  • GNU Make.
  • About 6 GB RAM available to Docker on macOS / Windows.

The 5-minute setup

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.

Bringing your code in

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 inside httpdocs/, 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.

Command reference

Run make help to see the same list grouped and coloured.

Setup

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).

Container lifecycle (parity with the original project script)

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.

Shells

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.

Networking helpers

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.

Magento workflows

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.

Day-to-day

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

Debugging

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.

How container IPs work

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.

Xdebug

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 mapping httpdocs/ -> /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.

Toggling Xdebug at runtime

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 / OFF

Toggling 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 URLs

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

How make configure works

The initializer asks for:

  1. Project name (used as the Docker network, volume prefix, container prefix).
  2. Local domain (e.g. myproject.local).
  3. Platform: Magento 2 (Open Source / Adobe Commerce) vs MageOS.
  4. Version (sorted newest-first).
  5. PHP version (only the versions compatible with the chosen Magento release).
  6. Database engine and version.
  7. Whether to enable Varnish.
  8. 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.

Repository layout

.
├── 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

Compatibility matrix

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.

Contributing

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.

License

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.