Installing CMA in an existing Magento project
CMA does not have to own your project from day one. If you already have a Magento 2 checkout — cloned from your team's repository, migrated from another Docker setup, or unpacked from a customer handover - you can add CMA to it and keep everything that is already there.
This page covers adding CMA to a Magento 2 project. If you are coming from our older magento-docker setup, read Converting legacy Docker setup to CMA instead - it covers the ScandiPWA-specific steps as well.
What changes when CMA manages your project
In a self-managed setup you own the infrastructure: you install and version-match every service, write the configs, pick the ports, and wire env.php by hand. CMA replaces all of that with a managed, per-project Docker stack derived from the Magento version in your composer.json. Your code, your database content, and your own scripts stay yours.
Services
Install and configure nginx, PHP-FPM, a database, Redis, a search engine, a mail catcher, SSL, optionally Varnish
One start runs the full stack as containers: nginx, two PHP-FPM (one with Xdebug), MariaDB, Redis, ElasticSearch or OpenSearch, MailDev, an SSL-terminating proxy, optional Varnish
Service versions
Track Adobe's requirements matrix; upgrade each component when Magento bumps it
Every service version is pinned to your Magento version — a Magento upgrade swaps the whole stack for you
PHP
Install the right PHP and ~28 extensions per project; switch versions between projects
A prebuilt container image matching your Magento version; your host PHP is irrelevant
Ports
Hardcode host ports, resolve clashes between projects
Assigned automatically, cached per project, bound to 127.0.0.1; several CMA projects run side by side
Wiring
Hand-edit env.php hosts and base URLs after every change
env.php and the base URLs are rewritten to point at the containers on every start
Daily commands
docker exec, docker logs, mysql client incantations
npm run cli (a shell with magento, composer, mariadb aliases), exec, logs, status
Debugging
Toggle Xdebug ini and restart PHP-FPM
A second PHP-FPM container with Xdebug is always ready; enabled per request via browser cookie, or debugphp for CLI
IDE setup
Hand-written launch.json and PhpStorm config
.vscode/ and .idea/ are generated and kept in sync with the assigned ports
What CMA decides for you
The convenience comes with opinionated defaults. These are the ones an existing project is most likely to notice:
The database is fixed. MariaDB, database name
magento, usermagento/magento, root passwordscandipwa. None of it is configurable - relevant if your dump references another schema name.npm run statusprints the credentials and ports.An admin user is ensured on every start. If the user from
cma.js(defaultadmin/scandipwa123) is missing - including right after a database import — it is created.Two-factor auth is disabled by adding
markshust/magento2-module-disabletwofactorauthas a dev dependency - this edits yourcomposer.jsonandcomposer.lock. Composer plugin approvals (allow-plugins) may also be written back tocomposer.json.All outgoing mail goes to the bundled MailDev mail catcher; the SMTP settings in your database are overwritten on every start. Nothing is ever delivered for real.
The
block_htmlandlayoutcaches are disabled on every start (andfull_pagetoo while Varnish is off - which it is by default).There is no cron. CMA neither installs a crontab nor runs Magento's cron. Indexers, queued emails, and every other scheduled job run only when you run
bin/magento cron:runyourself, e.g. throughnpm run exec.env.phpis re-serialized when CMA rewrites it, so comments and custom formatting in that file are lost.Generated service configs are disposable. They live in
node_modules/.create-magento-app-cache/and are regenerated on every start — customize them through the template options incma.js, not by editing the generated files.
Before you start
Your project should be a Magento 2 root — the folder that holds composer.json, bin/magento and app/etc.
Make sure you have:
All the prerequisites installed for your platform (Docker, Node.js 16 or newer, and the OS packages listed there).
Composer credentials for
repo.magento.com, either as anauth.jsonfile in the project root or in theCOMPOSER_AUTHenvironment variable. CMA checks for both and will prompt you if neither is present.Your Magento version in the list of supported versions.
Any previous stack for this project stopped — if it was running under another Docker setup or native services, shut those down first so the two do not compete for containers and volumes.
CMA refuses to run as root. Run every command below as your normal user.
Add CMA to the project
Point the generator at your existing project instead of a new folder name:
CMA detects that the folder is not empty and switches to adopt mode. In this mode it:
merges
package.json— the CMA scripts and the@scandipwa/magento-scriptsdependency are added, and everything you already have (your own scripts, dependencies, indentation) is kept as-is;creates
cma.js,AGENTS.md,CLAUDE.mdandREADME.mdonly if they do not exist yet;refreshes its own agent instructions under
.claude/magento-scripts/and.agents/magento-scripts/;installs the Node dependencies.
At the end it prints exactly what it created, what it merged, and what it left alone. The full list of files CMA may add, with commit guidance, is in What CMA adds to your project below.
Doing it by hand
If you would rather not run the generator, install the package directly:
Then add the scripts to your package.json:
Review cma.js
cma.js is the project's configuration file. The generator creates it from a default template — it does not inspect your project — so review it before the first start:
Three values are worth checking:
edition— the template defaults tocommunity. If yourcomposer.jsonrequiresmagento/product-enterprise-edition, set it toenterprise; CMA fails fast when the two contradict.adminuri— written intobackend.frontNameinapp/etc/env.phpon every start. If your project uses a custom admin path, set it here first.user/password— the admin account CMA creates when missing. Change them if the defaults are not acceptable even locally.
First run
How you run CMA for the first time depends on whether you have a database dump.
With a database dump
Skip start and go straight to the import — import-db is self-contained: it stops running containers, assigns ports, starts the services, and waits for MariaDB itself.
Beyond importing (see Importing database for the details), it also adapts the dump for local use: it asks before dropping and recreating the magento database, removes a custom admin path in favor of adminuri from cma.js, points the search-engine configuration at the local container, and offers to strip customers, orders and admin users from the imported data. Base URLs are handled automatically — both import-db and every later start set them to match the assigned ports.
Without a dump
With an empty database, the first start installs a blank Magento.
What happens on the first start
Reads your Magento version from
magento/product-community-edition(or-enterprise-edition) incomposer.json, falling back tocomposer.lock. You are not asked to pick a version.Adjusts
composer.jsonif needed — adds therepo.magento.comrepository andmagento/composer-root-update-pluginwhen they are missing.Runs
composer installwhen the installed state is incomplete or out of sync withcomposer.lock; skipped when everything already lines up.Generates service configs into
node_modules/.create-magento-app-cacheand starts the containers on free ports.Rewrites
app/etc/env.phpto point at those containers (see below).Sets up the database based on what
setup:db:statusreports: everything up to date — nothing runs; schema changes pending —setup:upgrade; Magento reports it is not installed at all (a non-empty database without a valid installation state) —setup:installfollowed bysetup:upgrade.
Every start — first or not — also applies the managed defaults described in What CMA decides for you: the admin user, the 2FA module, the cache and SMTP changes, and the base URLs.
When the start finishes, CMA prints the storefront, admin and MailDev URLs. npm run status shows them again at any time, along with the database credentials; npm run cli opens a shell inside the stack with magento, composer and mariadb ready as aliases.
What happens to app/etc/env.php
If the file exists, CMA edits it in place rather than replacing it, but several keys are rewritten on every start:
backend.frontName
set to magento.adminuri from cma.js
db.connection.default.host
pointed at the MariaDB container
session.redis.host / .port
pointed at the Redis container (when session.save is redis)
cache.frontend.default.backend_options
server and port pointed at the Redis container
http_cache_hosts
replaced with CMA's Varnish host, or removed when Varnish is disabled
cache.persisted-query
added or removed based on composer.lock
The file is re-serialized on rewrite, so comments and custom formatting in it do not survive.
Commit or back up app/etc/env.php before the first start. Existing http_cache_hosts entries are dropped, and a cache.persisted-query block is kept only when it comes from scandipwa/persisted-query.
What CMA adds to your project
cma.js
project configuration
yes
package.json
CMA scripts and dependency
yes
README.md
project readme with the CMA commands
yes
AGENTS.md, CLAUDE.md, .claude/, .agents/
AI agent instructions for the CMA commands
yes
node_modules/.create-magento-app-cache/
generated nginx / PHP / Varnish / MariaDB configs
no
.vscode/launch.json
XDebug launch configuration
your call
.idea/
PhpStorm PHP, XDebug and database settings
your call
.git/hooks/post-checkout
warns on composer.json / composer.lock changes after a branch switch
n/a
README.md, AGENTS.md, CLAUDE.md and cma.js are created only when missing; the .claude/magento-scripts/ and .agents/magento-scripts/ directories are CMA-managed and refreshed on every run. If your project uses husky or another hook manager, the git hook goes into that manager's directory instead of .git/hooks; an existing post-checkout hook is never overwritten.
Make sure node_modules is ignored in your .gitignore — that is where CMA keeps its generated service configs.
Things to watch out for
Version constraints must be exact. CMA looks up your version string against its list of supported versions. ^2.4.7 works (the caret is stripped) and 2.4.7-p3 works, but a constraint like ~2.4.7 or >=2.4.6 fails with No config found for magento version ~2.4.7. Pin an exact supported version in composer.json.
Edition mismatches abort the start. CMA fails fast when composer.json requires both editions at once, or when the edition there contradicts edition in cma.js — see Review cma.js.
A start script may already be taken. CMA never overwrites it — see the hint in Add CMA to the project.
vendor/ is validated against composer.lock. Packages that are in vendor/ but not in the lock file — a manually dropped module, for instance — make CMA treat the folder as corrupted and re-run composer install.
Last updated