Magento 2 Local Environment with Docker and Warden
A Magento 2 project has more moving parts than most PHP applications. PHP-FPM, a web server, MySQL or MariaDB, OpenSearch, Redis or Valkey, Varnish, RabbitMQ and a mail catcher all need to run together, in versions that match production. Installing all of that on a laptop by hand works until the second project needs a different PHP version.
Docker solves the version problem. Warden, an open source tool built around Docker Compose, solves the wiring. This article walks through a Warden setup for Magento 2.4.8, from the first env-init to a working store with Xdebug, and the habits that keep it working.
Why a containerized environment
The main point is parity. If production runs PHP 8.3 with OpenSearch 2 and MariaDB, the local environment should run the same. Bugs that only show up with a specific MySQL mode or a specific OpenSearch analyzer are much easier to catch when the versions match.
The second point is isolation. Each project gets its own containers, its own database and its own hostnames. You can stop one project and start another without uninstalling anything, and a new developer can get a running store from a repository and a database dump instead of a page of setup notes.
What Warden gives you
Warden runs a few global services once per machine and then one stack per project:
- Traefik as a reverse proxy, so every project gets a hostname like
app.myshop.test - A local root CA, so those hostnames work over HTTPS without browser warnings
- dnsmasq for resolving
*.testto your machine - Mailhog (or Mailpit, depending on version) to catch outgoing email
- Per project containers for nginx, PHP-FPM, a separate PHP-FPM with Xdebug, the database, Redis, OpenSearch, Varnish and RabbitMQ, each one switchable in the project
.env
On macOS, Warden syncs the code into the containers with Mutagen instead of a plain bind mount. That is the difference between a usable environment and one where every page takes several seconds.
First run
Install Docker and Warden, then start the global services once:
warden svc up
Inside the project folder, create the environment file and a certificate for the domain:
cd ~/Sites/myshop
warden env-init myshop magento2
warden sign-certificate myshop.test
env-init writes a .env file. This file is the real configuration of the stack, so read it and adjust it before starting anything.
The .env file
A trimmed example for a 2.4.8 project:
WARDEN_ENV_NAME=myshop
WARDEN_ENV_TYPE=magento2
WARDEN_WEB_ROOT=/
TRAEFIK_DOMAIN=myshop.test
TRAEFIK_SUBDOMAIN=app
WARDEN_DB=1
WARDEN_OPENSEARCH=1
WARDEN_ELASTICSEARCH=0
WARDEN_VARNISH=1
WARDEN_RABBITMQ=1
WARDEN_REDIS=1
PHP_VERSION=8.3
COMPOSER_VERSION=2
MARIADB_VERSION=11.4
OPENSEARCH_VERSION=2.12
RABBITMQ_VERSION=3.13
REDIS_VERSION=7.2
VARNISH_VERSION=7.4
The exact variable names and the versions available depend on the Warden release you have, so check the generated file instead of copying this one. The important habit is to set every version to what production runs, and to commit the .env to the repository so everyone uses the same stack.
If you do not need a service, turn it off. Varnish and RabbitMQ use memory, and many tasks do not need them. You can switch them back on when you work on full page cache or queue consumers.
Starting the environment
warden env up
warden shell
warden env up pulls the images and starts the project containers. warden shell opens a shell in the PHP container as the www-data user, in the project root. Everything that touches Magento runs from there: Composer, bin/magento, PHPUnit.
For one-off commands I prefer not to open an interactive shell:
warden shell -c "bin/magento cache:flush"
warden shell -c "composer install"
Avoid running bin/magento on the host. The host PHP is a different version with different extensions, and the hostnames db, redis and opensearch only resolve inside the Docker network. Running docker exec directly also works, but it is easy to end up as root and create files that the PHP-FPM user cannot write later.
Installing a fresh Magento
For a new project, create it with Composer from inside the shell and then run the installer pointing at the container hostnames:
bin/magento setup:install \
--backend-frontname=backend \
--db-host=db --db-name=magento \
--db-user=magento --db-password=magento \
--search-engine=opensearch \
--opensearch-host=opensearch --opensearch-port=9200 \
--opensearch-index-prefix=magento2 --opensearch-enable-auth=0 \
--amqp-host=rabbitmq --amqp-port=5672 \
--amqp-user=guest --amqp-password=guest \
--session-save=redis --session-save-redis-host=redis \
--session-save-redis-db=2 \
--cache-backend=redis --cache-backend-redis-server=redis \
--cache-backend-redis-db=0 \
--page-cache=redis --page-cache-redis-server=redis \
--page-cache-redis-db=1 \
--http-cache-hosts=varnish:80
bin/magento config:set web/unsecure/base_url https://app.myshop.test/
bin/magento config:set web/secure/base_url https://app.myshop.test/
bin/magento deploy:mode:set developer
The magento / magento database credentials are the Warden defaults. Every option above ends up in app/etc/env.php, which is covered in more detail in env.php and config.php explained.
Importing an existing project
Most of the time you join a project that already exists. The steps are:
- Clone the repository and run
warden env up - Run
composer installinside the container - Import a sanitized database dump
- Create or adjust
app/etc/env.phpfor the container hostnames - Fix base URLs and run
setup:upgrade
The database import streams the dump into the database container:
gunzip -c ~/dumps/myshop.sql.gz | warden db import
Then, inside the shell:
bin/magento setup:upgrade
bin/magento indexer:reindex
bin/magento cache:flush
Never import a raw production dump without cleaning it. Customer emails, payment gateway credentials and integration endpoints in core_config_data can make a local store send real emails or call real APIs. The database sanitizing article covers what to remove.
Xdebug
Warden runs a second PHP-FPM container with Xdebug loaded. Requests go to it only when the XDEBUG_SESSION cookie is present, so normal page loads stay fast. Install a browser extension that sets the cookie, configure PhpStorm with a server whose name matches the domain and whose path mapping points the project root to /var/www/html, and start listening.
For CLI commands, use the debug shell:
warden debug
The full PhpStorm setup and the common reasons breakpoints are ignored are in debugging Magento 2 with Xdebug.
Day to day habits
Keep developer mode and the right caches
Developer mode shows errors and generates code on demand. Keep most caches enabled even in developer mode and only disable the ones you are working against, usually layout, block_html and full_page for frontend work. A store with every cache off is slower than it needs to be and hides cache related bugs.
Clean generated code with intent
When a constructor signature changes, delete generated/code for the affected classes or the whole folder. Clearing everything after every change wastes time, since Magento regenerates it on the next request.
Stop what you are not using
Each project with OpenSearch, MariaDB and RabbitMQ can take several gigabytes of RAM. Run warden env down when you switch projects. The database lives in a Docker volume, so it survives a stop.
Read the mail catcher
Transactional emails go to the global mail catcher at webmail.warden.test. That is the place to test order and customer email templates without sending anything real.
Common problems
- Browser says the certificate is invalid. The Warden root CA was not trusted, or the certificate was signed for a different domain than
TRAEFIK_DOMAIN. Runsign-certificateagain with the right name and restart. - Redirect loop or redirect to production. Base URLs in
core_config_datastill point to the live domain. Update them withconfig:setor directly in the database. - OpenSearch container exits. Usually memory. Give Docker more RAM or reduce the heap in the service configuration.
- Files owned by root. Someone ran commands as root in the container. Fix ownership and go back to
warden shell.
Once the environment is stable, the same container setup is a good base for running tests locally. See Magento 2 integration tests for running the test framework against a separate database in the same stack.
FAQ
Is Warden better than docker-magento or DDEV for Magento 2?
All three work. Warden gives each project its own stack behind a shared Traefik proxy with local TLS, and its .env file keeps service versions close to production. Pick the one your team will actually maintain.
Why is Magento slow in Docker on macOS?
Bind mounts between macOS and the Linux VM are slow for a codebase with tens of thousands of files. Warden syncs files with Mutagen on macOS to avoid that. Also give Docker enough memory, since OpenSearch and MySQL both need it.
Should I run bin/magento on the host or inside the container?
Inside the container. The PHP version, extensions and hostnames like db and redis only exist there. Use warden shell or warden shell -c with the command.
Can I use a production database dump locally?
Yes, but sanitize it first. Remove or anonymize customer data, change base URLs, and disable payment, email and integration settings so the local copy cannot talk to real services.
I am available for freelance Magento and Adobe Commerce work, including setting up local environments for teams. If you need help with this, get in touch.
Related articles
Magento 2 Backup and Restore: What to Save and How
How to back up and restore a Magento 2 store: database dumps that import cleanly, media, env.php and the encryption key, offsite copies and tested restores.
Magento 2 CI/CD Pipeline with GitHub Actions
Build a Magento 2.4.8 CI/CD pipeline in GitHub Actions: Composer auth, coding standard, PHPStan, unit tests, a DB-free build and an atomic release deploy.
Magento 2 RabbitMQ Consumers That Don't Die Silently
Set up RabbitMQ message queues in Magento 2.4 the right way: queue XML files, consumers run by cron or supervisor, max-messages and how to spot a dead consumer.
Zero Downtime Magento 2 Deploys on a Single Server
A practical zero downtime deploy for Magento 2.4 on one server: build in a release folder, compile outside live, switch a symlink and roll back in seconds.