> For the complete documentation index, see [llms.txt](https://docs.create-magento-app.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.create-magento-app.com/usage-guide/installing-cma-in-an-existing-magento-project.md).

# 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](https://github.com/scandipwa/magento-docker) setup, read [Converting legacy Docker setup to CMA](/usage-guide/converting-legacy-docker-setup-to-cma.md) 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.

<table data-header-hidden="false" data-header-sticky data-search="false"><thead><tr><th width="153.55859375">Area</th><th>You manage in a vanilla setup</th><th width="343.976806640625">CMA manages for you</th></tr></thead><tbody><tr><td>Services</td><td>Install and configure nginx, PHP-FPM, a database, Redis, a search engine, a mail catcher, SSL, optionally Varnish</td><td>One <code>start</code> 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</td></tr><tr><td>Service versions</td><td>Track Adobe's requirements matrix; upgrade each component when Magento bumps it</td><td>Every service version is pinned to your Magento version — a Magento upgrade swaps the whole stack for you</td></tr><tr><td>PHP</td><td>Install the right PHP and ~28 extensions per project; switch versions between projects</td><td>A prebuilt container image matching your Magento version; your host PHP is irrelevant</td></tr><tr><td>Ports</td><td>Hardcode host ports, resolve clashes between projects</td><td>Assigned automatically, cached per project, bound to <code>127.0.0.1</code>; several CMA projects run side by side</td></tr><tr><td>Wiring</td><td>Hand-edit <code>env.php</code> hosts and base URLs after every change</td><td><code>env.php</code> and the base URLs are rewritten to point at the containers on every start</td></tr><tr><td>Daily commands</td><td><code>docker exec</code>, <code>docker logs</code>, mysql client incantations</td><td><code>npm run cli</code> (a shell with <code>magento</code>, <code>composer</code>, <code>mariadb</code> aliases), <code>exec</code>, <code>logs</code>, <code>status</code></td></tr><tr><td>Debugging</td><td>Toggle Xdebug ini and restart PHP-FPM</td><td>A second PHP-FPM container with Xdebug is always ready; enabled per request via browser cookie, or <code>debugphp</code> for CLI</td></tr><tr><td>IDE setup</td><td>Hand-written <code>launch.json</code> and PhpStorm config</td><td><code>.vscode/</code> and <code>.idea/</code> are generated and kept in sync with the assigned ports</td></tr></tbody></table>

### 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`, user `magento`/`magento`, root password `scandipwa`. None of it is configurable - relevant if your dump references another schema name. `npm run status` prints the credentials and ports.
* **An admin user is ensured on every start.** If the user from `cma.js` (default `admin` / `scandipwa123`) is missing - including right after a database import — it is created.
* **Two-factor auth is disabled** by adding `markshust/magento2-module-disabletwofactorauth` as a dev dependency - this edits your `composer.json` and `composer.lock`. Composer plugin approvals (`allow-plugins`) may also be written back to `composer.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_html` and `layout` caches are disabled on every start** (and `full_page` too 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:run` yourself, e.g. through `npm run exec`.
* **`env.php` is 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 in `cma.js`, not by editing the generated files.

{% hint style="info" %}
Need a start without the Magento-side changes? `npm run start -- --skip-setup` skips the whole setup phase — the `env.php` rewrite, admin user, 2FA module, cache and SMTP changes — for that run.
{% endhint %}

## 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](/getting-started/prerequisites.md) installed for your platform (Docker, Node.js 16 or newer, and the OS packages listed there).
* Composer credentials for `repo.magento.com`, either as an `auth.json` file in the project root or in the `COMPOSER_AUTH` environment variable. CMA checks for both and will prompt you if neither is present.
* Your Magento version in [the list of supported versions](/getting-started/supported-magento-versions.md).
* 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.

{% hint style="warning" %}
CMA refuses to run as `root`. Run every command below as your normal user.
{% endhint %}

{% stepper %}
{% step %}

## Add CMA to the project

Point the generator at your existing project instead of a new folder name:

```bash
cd /path/to/your/magento/project

npx create-magento-app .
```

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-scripts` dependency are added, and everything you already have (your own scripts, dependencies, indentation) is kept as-is;
* **creates** `cma.js`, `AGENTS.md`, `CLAUDE.md` and `README.md` **only 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](#what-cma-adds-to-your-project) below.

{% hint style="info" %}
If your `package.json` already defines a script CMA also wants — most commonly `start`, used by frontend tooling — CMA does **not** overwrite it. It reports the clash instead, and you can reach that command through `npx magento-scripts start`.
{% endhint %}

### Doing it by hand

If you would rather not run the generator, install the package directly:

```bash
# only if the project has no package.json yet
npm init -y

npm i @scandipwa/magento-scripts@latest
```

Then add the scripts to your `package.json`:

{% code title="package.json" %}

```json
{
    "scripts": {
        "start": "magento-scripts start",
        "stop": "magento-scripts stop",
        "cli": "magento-scripts cli",
        "logs": "magento-scripts logs",
        "link": "magento-scripts link",
        "status": "magento-scripts status",
        "exec": "magento-scripts exec",
        "import-db": "magento-scripts import-db"
    }
}
```

{% endcode %}
{% endstep %}

{% step %}

## Review `cma.js`

`cma.js` is the project's [configuration file](/getting-started/config-file.md). The generator creates it from a default template — it does **not** inspect your project — so review it before the first start:

{% code title="cma.js" %}

```javascript
/** @type {import('@scandipwa/magento-scripts').CMAConfiguration} */
module.exports = {
    magento: {
        first_name: 'Scandiweb',
        last_name: 'Developer',
        email: 'developer@scandipwa.com',
        // the admin account CMA ensures exists on every start
        user: 'admin',
        password: 'scandipwa123',
        // must match the admin path your project already uses —
        // it is written into app/etc/env.php on every start
        adminuri: 'admin',
        mode: 'developer',
        // 'community' or 'enterprise' — must match composer.json
        edition: 'community'
    },
    configuration: {}
}
```

{% endcode %}

Three values are worth checking:

* **`edition`** — the template defaults to `community`. If your `composer.json` requires `magento/product-enterprise-edition`, set it to `enterprise`; CMA fails fast when the two contradict.
* **`adminuri`** — written into `backend.frontName` in `app/etc/env.php` on 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.
  {% endstep %}

{% step %}

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

```bash
npm run import-db -- ./dump.sql
```

Beyond importing (see [Importing database](/usage-guide/importing-database.md) 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

```bash
npm run start
```

With an empty database, the first start installs a blank Magento.

### What happens on the first start

1. **Reads your Magento version** from `magento/product-community-edition` (or `-enterprise-edition`) in `composer.json`, falling back to `composer.lock`. You are not asked to pick a version.
2. **Adjusts `composer.json`** if needed — adds the `repo.magento.com` repository and `magento/composer-root-update-plugin` when they are missing.
3. **Runs `composer install`** when the installed state is incomplete or out of sync with `composer.lock`; skipped when everything already lines up.
4. **Generates service configs** into `node_modules/.create-magento-app-cache` and starts the containers on free ports.
5. **Rewrites `app/etc/env.php`** to point at those containers (see below).
6. **Sets up the database** based on what `setup:db:status` reports: 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:install` followed by `setup:upgrade`.

Every start — first or not — also applies the managed defaults described in [What CMA decides for you](#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:

| Key                                      | What CMA does                                                         |
| ---------------------------------------- | --------------------------------------------------------------------- |
| `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.

{% hint style="warning" %}
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`.
{% endhint %}
{% endstep %}
{% endstepper %}

## What CMA adds to your project

<table data-search="false"><thead><tr><th>Path</th><th>Purpose</th><th>Commit it?</th></tr></thead><tbody><tr><td><code>cma.js</code></td><td>project configuration</td><td>yes</td></tr><tr><td><code>package.json</code></td><td>CMA scripts and dependency</td><td>yes</td></tr><tr><td><code>README.md</code></td><td>project readme with the CMA commands</td><td>yes</td></tr><tr><td><code>AGENTS.md</code>, <code>CLAUDE.md</code>, <code>.claude/</code>, <code>.agents/</code></td><td>AI agent instructions for the CMA commands</td><td>yes</td></tr><tr><td><code>node_modules/.create-magento-app-cache/</code></td><td>generated nginx / PHP / Varnish / MariaDB configs</td><td>no</td></tr><tr><td><code>.vscode/launch.json</code></td><td>XDebug launch configuration</td><td>your call</td></tr><tr><td><code>.idea/</code></td><td>PhpStorm PHP, XDebug and database settings</td><td>your call</td></tr><tr><td><code>.git/hooks/post-checkout</code></td><td>warns on <code>composer.json</code> / <code>composer.lock</code> changes after a branch switch</td><td>n/a</td></tr></tbody></table>

`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`](#review-cmajs).

**A `start` script may already be taken.** CMA never overwrites it — see the hint in [Add CMA to the project](#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`.
