Skip to main content

Running multiple local environments

The main checkout of nexgen_v3 runs the dev environment described in Nexgen V3 local development. This page covers the three extra environments, copy1, copy2, and copy3, that git worktrees of the same repo can use. Each one has its own databases, its own backend containers, and its own ports, so work in one worktree never changes the data another worktree is testing against.

One script, docker/worktree-env.sh in the nexgen_v3 repo, creates the databases, assigns an environment to a worktree, and starts it.

How the pieces fit​

The dev environment stays as it is: the main checkout, the default ports, and the databases nexgen_v3, nexgen_free_v3, nexgen_test, and nexgen_mdb_v3.

Environment N, where N is 1, 2, or 3, has:

  • Three MySQL databases on the native server: nexgen_v3_copyN (paid app), nexgen_free_v3_copyN (trial app), and nexgen_test_copyN (the isolated database for the backend tests).
  • One MongoDB database on the native server, nexgen_mdb_v3_copyN, shared by that environment's paid and trial backends, the same way dev shares nexgen_mdb_v3.
  • Its own Docker Compose project, npv3-copyN, with five containers: nexgen-backend, nexgen-free-backend, api, mcp-server, and mailpit. The container names are npv3-copyN-<service>.
  • The frontends, which run on the Mac with ng serve. No frontend containers are started.
  • The dev ports plus N x 100.

The ports for each environment:

  • Paid frontend: 4200 on dev, then 4300, 4400, and 4500.
  • Trial frontend: 4201 on dev, then 4301, 4401, and 4501.
  • Paid backend: 8010 on dev, then 8110, 8210, and 8310.
  • Trial backend: 8011 on dev, then 8111, 8211, and 8311.
  • The api service: 5050 on dev, then 5150, 5250, and 5350.
  • The mcp-server: 3001 on dev, then 3101, 3201, and 3301.
  • Mailpit: 8025 on dev, then 8125, 8225, and 8325.

The MySQL and MongoDB servers are shared. Only the database names differ, so one pair of servers holds the dev data and all three copies.

How a worktree knows its environment​

Assigning an environment writes a line such as NPV3_ENV=copy1 to the gitignored .env file in the worktree root, together with that environment's Compose project name, URLs, and database names. Three tools read it, so commands run inside the worktree reach its own stack with no extra flags:

  • Docker Compose reads the root .env for the project name. docker compose exec nexgen-backend ... run inside the worktree reaches the npv3-copyN containers.
  • Playwright loads the root .env before playwright.local.env, so the backend, frontend, Mailpit, and MCP URLs point at the worktree's own ports.
  • The backend containers carry NPV3_DB_SUFFIX=_copyN. The PHPUnit setup appends it to the database a PHPUnit config names, so nexgen_test becomes nexgen_test_copyN and the seeded config's nexgen_v3 becomes nexgen_v3_copyN.

Assigning also writes these gitignored files into the worktree:

  • docker-compose.override.yml, which gives the stack its container names, ports, and database names, and keeps the database and frontend containers from starting.
  • nexgen/backend/.env and docker/nexgen-free.env, copied from the main checkout. A new worktree does not have them.
  • nexgen/frontend/src/environments/environment.local.ts, environment-trial.local.ts, and nexgen/frontend/proxy.conf.local.json, which point ng serve at the worktree's own backends. The tracked environment.ts files are left alone.

docker/worktree-env.sh status prints which worktree has which environment, and the URLs and databases of the one it is run from.

Checking which environments are running​

status shows assignments only, not whether an environment's containers are up. Each environment is a Docker Compose project, named nexgen_v3 for the dev environment and npv3-copy1 to npv3-copy3 for the worktrees, so Docker can list the running containers grouped by environment:

docker ps --format '{{.Label "com.docker.compose.project"}}\t{{.Names}}\t{{.Status}}' | sort

For a count of running and stopped containers per environment, including environments that are stopped:

docker ps -a --format '{{.Label "com.docker.compose.project"}}\t{{.State}}' | sort | uniq -c

An environment that has been assigned but never started does not appear in either list. A worktree environment that is up has five containers. The dev environment has more, because it also runs the frontends, S3, and any database containers.

Why the test database is copied too​

nexgen_test cannot be shared by worktrees running at the same time. The backend feature tests rebuild it with migrate:fresh at the start of a run, which drops every table, and worktrees on different branches carry different migrations. Each environment gets its own nexgen_test_copyN. It is created empty and the first test run migrates it.

Before you start​

  • A working dev environment with the native databases, set up as described in Nexgen V3 local development. The copies are made from the dev databases, and the worktrees take their environment files from the main checkout.
  • The nexgen_v3 commit that added docker/worktree-env.sh on the branch each worktree is created from. A worktree on an older branch lacks the Angular local build configurations, the PHPUnit database suffix, and the Playwright URL settings.
  • Node.js on the Mac for ng serve. Node.js 20 is known to work.
  • Disk space. Each environment adds about 2 GB of databases, and each worktree adds its own vendor and node_modules folders and its own Docker images.
  • Memory. Each ng serve grows to about 4.4 GB, close to the 4 GB heap limit the script gives it. An environment's five containers together use under 1 GB.

Setting up​

1. Create the databases​

Run this once for each environment, from the main checkout. Each run copies the dev MySQL and MongoDB databases, which takes a few minutes.

cd ~/git/axxya/nexgen_v3
docker/worktree-env.sh db-create 1
docker/worktree-env.sh db-create 2
docker/worktree-env.sh db-create 3

For environment N, the command:

  • Creates the three MySQL databases and grants npuser, mcp_readonly, and flask_readonly the same rights they have on the dev databases.
  • Copies nexgen_v3 and nexgen_free_v3 into the two app databases. nexgen_test_copyN is left empty.
  • Creates the nexgen and flask_writer users on the MongoDB copy, then copies nexgen_mdb_v3 into it, indexes included.

The command is safe to run again. It skips any copy that already holds data, so it never overwrites one. The copies hold the dev data as it was when they were made. To bring one up to date later, see Resetting an environment's data.

The script connects to MySQL as root with the development password from docker-compose.yml. If the local root password differs, set NPV3_MYSQL_ROOT_PASSWORD for the command.

2. Create a worktree​

Create the worktree in Supacode with the new worktree button, or from a Supacode terminal:

supacode repo worktree-new --branch ai/copy1 --base main

Supacode puts the worktree in ~/.supacode/repos/nexgen_v3/<branch name>/, so the example above lands in ~/.supacode/repos/nexgen_v3/ai/copy1. The folder keeps that name if the branch is renamed later.

Worktrees made with plain git

A worktree created with git worktree add works with the script in the same way. It did not appear in the Supacode sidebar when this was set up, so use Supacode to create worktrees that should be managed there.

The branch name has no effect on the environment. The script never reads it.

3. Assign an environment to the worktree​

From the main checkout, give the environment number and the worktree path:

cd ~/git/axxya/nexgen_v3
docker/worktree-env.sh assign 1 ~/.supacode/repos/nexgen_v3/ai/copy1
docker/worktree-env.sh status

assign writes the files listed under How a worktree knows its environment. It refuses to assign an environment that another worktree already holds, and it refuses to assign one to the main checkout.

4. Start the containers​

cd ~/.supacode/repos/nexgen_v3/ai/copy1
docker/worktree-env.sh up
curl -s http://localhost:8110/version

up builds the environment's images, starts its five containers, runs composer install if the worktree has no vendor folder, and prepares the storage folders. The /version response names the database, which should be nexgen_v3_copy1 on port 8110 and nexgen_free_v3_copy1 on port 8111.

The first up on a machine is slow. The backend image is built for linux/amd64 under emulation, which took about 17 minutes the first time. Later environments reuse the cached build steps.

5. Start a frontend​

serve runs in the foreground, so give it its own terminal. Start only the frontend the work needs.

cd ~/.supacode/repos/nexgen_v3/ai/copy1
docker/worktree-env.sh serve paid

Use serve trial for the trial app. The first run in a worktree does an npm install. When the compile finishes, the paid app for copy1 is at http://localhost:4300 and the trial app at http://localhost:4301. Stop a frontend with Ctrl+C.

6. Check the environment​

Log in at the paid frontend with the same seeded logins as dev, for example test@axxya.com / testpass@word1. Then run a backend test and a workflow suite from the worktree:

cd ~/.supacode/repos/nexgen_v3/ai/copy1
npm install
docker compose exec nexgen-backend php artisan test tests/Feature/ShopInactiveProductTest.php
npx playwright test --project=nexgen-workflow

The npm install in the worktree root installs Playwright, and is needed once for each worktree.

Using an environment​

Finding out which environment a checkout is in​

docker/worktree-env.sh status

Run it from any checkout. It lists every worktree with its environment, and for the checkout it is run from it prints the URLs and database names. The main checkout reports the dev environment. A worktree with no environment reports that none is assigned, and nothing should be run from it against the dev stack.

The repo's CLAUDE.md tells a Claude Code session to run this command before it starts the apps or any tests, so a session working in a worktree uses that worktree's environment and leaves the dev databases alone.

Running commands in the containers​

Inside a worktree, use docker compose exec with the service name. Compose resolves it to that environment's container.

docker compose exec nexgen-backend php artisan migrate --force
docker compose exec nexgen-free-backend php artisan migrate --force
docker compose exec nexgen-backend php artisan view:clear
docker compose logs -f nexgen-backend
Do not use the dev container names

docker exec npv3-nexgen-backend ... always reaches the dev stack, whichever folder it is run from. In a worktree environment, use docker compose exec nexgen-backend ..., or the full container name npv3-copyN-nexgen-backend.

The Laravel log for an environment is in nexgen/backend/storage/logs/ inside its own worktree.

Running the tests​

The usual commands work unchanged inside the worktree:

docker compose exec nexgen-backend php artisan test
docker compose exec nexgen-backend ./vendor/bin/pest -c phpunit-seeded.xml
npx playwright test --project=nexgen-workflow
npx playwright test --project=nexgen-free-workflow
  • php artisan test runs against nexgen_test_copyN.
  • The seeded guards run against nexgen_v3_copyN.
  • The Playwright projects log in through the environment's backend ports and drive its frontends, so the matching frontend must be running: the paid one for the nexgen projects and the trial one for the nexgen-free projects.

The cluster projects, such as nexgen-staging-cluster, run against staging or the test cluster from any checkout. On a cluster run, Playwright ignores the worktree's local URLs and takes them from playwright.cluster.env.

Email​

Each environment has its own Mailpit, which catches every email its two backends send. For copy1 it is at http://localhost:8125.

Connecting to the databases​

The logins are the same as dev. Only the database names change.

mysql -h 127.0.0.1 -P 3306 -u npuser -pnppassword nexgen_v3_copy1
mongosh "mongodb://nexgen:nexgen@127.0.0.1:27017/nexgen_mdb_v3_copy1?authSource=nexgen_mdb_v3_copy1"

After pulling changes or switching branches​

A copy starts with the dev schema as it was when the copy was made. If the worktree's branch adds migrations, run them in both backend containers, and run the seeders if the branch changed them:

docker compose exec nexgen-backend php artisan migrate --force
docker compose exec nexgen-free-backend php artisan migrate --force
docker compose exec nexgen-backend php artisan db:seed --force
docker compose exec nexgen-free-backend php artisan db:seed --force

The two backends mount the worktree's source folder, so PHP changes take effect as they do on dev. The api and mcp-server images hold a copy of their source. After changing either one, run docker/worktree-env.sh up again to rebuild them.

Stopping and starting​

docker compose stop
docker/worktree-env.sh up

Run both from inside the worktree. stop turns the environment's containers off and keeps them. A stopped environment uses no memory, and its databases stay on the native servers.

Using several environments in one browser​

Each environment's backends use their own session cookie names, so one browser can stay logged in to dev and to every copy at the same time.

Resetting an environment's data​

db-reset drops the environment's four databases and copies them from dev again. Use it when test data has built up in a copy, or to pick up newer dev data.

cd ~/git/axxya/nexgen_v3
docker/worktree-env.sh db-reset 1
This deletes the copy's data

Everything in nexgen_v3_copy1, nexgen_free_v3_copy1, nexgen_test_copy1, and nexgen_mdb_v3_copy1 is lost. The command asks for the environment name to be typed before it does anything, and it can only ever drop a database whose name ends in _copy1, _copy2, or _copy3. The dev databases are only read.

Stop the environment's containers first, and run the migrations afterwards if the worktree's branch has any that dev does not.

Moving an environment to another worktree​

Stop the containers, release the environment, then assign it to the other worktree:

cd ~/.supacode/repos/nexgen_v3/ai/copy1
docker compose stop
docker/worktree-env.sh release
cd ~/git/axxya/nexgen_v3
docker/worktree-env.sh assign 1 /path/to/other/worktree

release removes the environment lines from the worktree's .env and deletes the generated override and frontend files. The databases are left as they are, so the next worktree starts with the data the last one left. Run db-reset first for a clean start.

Running the script for another worktree​

assign, release, up, and serve take a worktree path as their last argument, so the main checkout's copy of the script can drive any worktree. This is useful when a worktree's own copy of the script is older:

~/git/axxya/nexgen_v3/docker/worktree-env.sh up ~/.supacode/repos/nexgen_v3/ai/copy2
~/git/axxya/nexgen_v3/docker/worktree-env.sh serve paid ~/.supacode/repos/nexgen_v3/ai/copy2

What the environments share​

  • The MySQL and MongoDB servers, and their memory limits. Heavy work in one environment can slow the others.
  • The Chargebee test site, the Anthropic API key, and every other key in the backend environment files, which are copied from the main checkout. Tests that create Chargebee objects in two environments are writing to the same Chargebee site.
  • The V2 apps, which always use the dev databases.

Troubleshooting​

  • ng serve stops with "JavaScript heap out of memory". The build needs more heap than it was given. Set a larger limit for the command, for example NODE_OPTIONS=--max-old-space-size=6144 docker/worktree-env.sh serve paid.
  • ng serve reports that the configuration local is not set. The worktree's branch predates the script. Bring the branch up to date with main.
  • "has no environment assigned". up and serve only run in a worktree that holds an environment. Run assign for it first.
  • "is already assigned to" another worktree. Release it there first, as described under Moving an environment to another worktree.
  • "docker-compose.override.yml exists and was not generated by this script". The worktree has a hand-written override file. Move it aside, then run assign again.
  • A new worktree does not appear in Supacode. It was created with plain git. Create it through Supacode instead.
  • A test run changed the dev data. The run was started from a checkout with no environment, or from the main checkout. Check with docker/worktree-env.sh status.
  • db-create skipped a copy that is only partly loaded. An interrupted run leaves some tables behind, and the next run sees data and skips the load. Use db-reset for that environment.
  • A port is already in use. Another program holds one of the environment's ports. lsof -nP -iTCP:8110 -sTCP:LISTEN shows which.