Nexgen V3 local development
The local development environment runs the app services in Docker containers and the two databases directly on the Mac. MySQL and MongoDB are installed with Homebrew and are no longer run in Docker. Several worktrees of the repo can then share one pair of database servers, and the Docker VM stays small.
This page covers the paid app (nexgen) and the trial app (nexgen-free), which share one backend and one frontend codebase.
The setup here is the dev environment, which belongs to the main checkout. To give a git worktree an environment of its own, with separate databases, containers, and ports, see Running multiple local environments. That page builds on this one, so finish this setup first.
How the pieces fit
- On the Mac: MySQL 8.4 on port 3306 and MongoDB 8.0 on port 27017, both listening on
127.0.0.1only and both run bybrew services. - In Docker: the two backends, the two frontends, the mcp-server, the api service, and Mailpit, which catches outgoing email.
- The containers reach the databases through the host name
host.docker.internal.
The committed docker-compose.yml still defines mysql and mongo containers, on host ports 3307 and 27018. A gitignored docker-compose.override.yml puts them behind a profile so that they are not started.
docker/setup.sh starts the mysql and mongo containers, and applies its MySQL grants inside the npv3-mysql container. Do not use it for a native database setup. Follow the steps on this page, which run the same operations against the native servers.
Prerequisites
- A Mac with Homebrew.
- A current Docker Desktop. The override file uses the Docker Compose
!resettag, which old Compose versions reject. - Git, and access to the Axxya-Systems/nexgen_v3 repository.
- MySQL 8.4 (Homebrew
mysql@8.4). - MongoDB 8.0 (Homebrew
mongodb-community@8.0), withmongoshand the MongoDB database tools. - Optional: the
nexgen_v2repository cloned next tonexgen_v3, to run the V2 apps against the same databases.
brew install --cask docker
brew install mysql@8.4
brew tap mongodb/brew
brew install mongodb-community@8.0 mongosh mongodb-database-tools
mysql@8.4 is keg-only, so add its client to the path:
echo 'export PATH="/opt/homebrew/opt/mysql@8.4/bin:$PATH"' >> ~/.zshrc
Setting up the databases
1. Configure and start MySQL
Put these settings in /opt/homebrew/etc/my.cnf before the first start. They match what staging and production run, and the first two keep the V2 PHP 5.6 clients able to connect.
[mysqld]
bind-address = 127.0.0.1
mysqlx-bind-address = 127.0.0.1
port = 3306
mysql_native_password = ON
authentication_policy = *:mysql_native_password
sql-mode = NO_ENGINE_SUBSTITUTION
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
innodb_buffer_pool_size = 1G
brew services start mysql@8.4
brew services start mongodb-community@8.0
2. Create the databases and users
Clone the repo, then load the two init scripts into the native servers. A fresh Homebrew MySQL has a root user with no password; if a root password has been set since, add -p.
cd ~/git/axxya
git clone git@github.com:Axxya-Systems/nexgen_v3.git
cd nexgen_v3
mysql -u root < docker/init.sql
mongosh docker/init-mongo.js
init.sql creates the MySQL databases and the three users. init-mongo.js creates the MongoDB database, its users, and its indexes. Both are safe to run again.
Database logins
These are development-only credentials, committed in docker/init.sql and docker/init-mongo.js. They are never used on staging or production.
MySQL, at 127.0.0.1:3306:
npuser/nppasswordis the app user, with full rights onnexgen_v3(paid app),nexgen_free_v3(trial app), andnexgen_test(the isolated database the backend unit tests use).mcp_readonly/mcp_readonly_passwordis the read-only user for the mcp-server.flask_readonly/flask_readonly_passwordis the read-only user for the api service.
MongoDB, at 127.0.0.1:27017:
nexgen/nexgenon thenexgen_mdb_v3database, shared by the paid app and the trial app.flask_writer/flask_writeron the same database, for the api service.
To connect from a terminal:
mysql -h 127.0.0.1 -P 3306 -u npuser -pnppassword nexgen_v3
mongosh "mongodb://nexgen:nexgen@127.0.0.1:27017/nexgen_mdb_v3?authSource=nexgen_mdb_v3"
The local database names end in _v3. Staging and production use nexgen, nexgen_free, and nexgen_mdb.
Setting up the Docker containers
1. Create the environment files
These files are gitignored, so a fresh clone does not have them, and Docker Compose will not start without them.
cp nexgen/backend/.env.example nexgen/backend/.env
cp nexgen/backend/.env.example docker/nexgen-free.env
In both files, point the backends at the native databases:
DB_HOST=host.docker.internal
MONGO_DB_HOST=host.docker.internal
Then, in docker/nexgen-free.env only, apply the trial differences: DB_DATABASE=nexgen_free_v3, TRIAL_DOMAIN=true, its own APP_NAME, and the trial create-limit variables (INGREDIENT_CREATE, RECIPE_CREATE, LABEL_CREATE, and the others), set high for development. The MongoDB database stays nexgen_mdb_v3. Copying this file from another developer is the quickest route.
2. Create the override file
Create docker-compose.override.yml in the repo root. Docker Compose merges it over docker-compose.yml automatically, and it is gitignored.
services:
mysql:
profiles: ["docker-dbs"]
mongo:
profiles: ["docker-dbs"]
nexgen-backend:
depends_on: !reset []
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
JWT_PRIVATE_KEY: "<base64 private key>"
nexgen-free-backend:
depends_on: !reset []
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
JWT_PRIVATE_KEY: "<base64 private key>"
mcp-server:
depends_on: !reset []
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
MYSQL_HOST: "host.docker.internal"
MONGO_URL: "mongodb://nexgen:nexgen@host.docker.internal:27017/nexgen_mdb_v3?authSource=nexgen_mdb_v3"
api:
depends_on: !reset []
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
JWT_PUBLIC_KEY: "<base64 public key>"
MYSQL_HOST: "host.docker.internal"
MONGO_URL: "mongodb://flask_writer:flask_writer@host.docker.internal:27017/nexgen_mdb_v3?authSource=nexgen_mdb_v3"
The backends sign a token with the private key and the api service verifies it with the public key. Without the keys, the AI features return an error and everything else works. To generate a pair:
openssl genpkey -algorithm RSA -out jwt-private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem
base64 -i jwt-private.pem | tr -d '\n'
base64 -i jwt-public.pem | tr -d '\n'
Recipe import also needs ANTHROPIC_API_KEY, set in a gitignored .env file in the repo root.
3. Build and start the containers
Name the services, because docker-compose.yml also defines services that are no longer run locally.
docker compose build nexgen-backend nexgen-free-backend nexgen-frontend nexgen-free-frontend mcp-server api
docker compose up -d nexgen-backend nexgen-free-backend nexgen-frontend nexgen-free-frontend mcp-server api mailpit
4. Install dependencies and prepare storage
The two backend containers mount the same source folder, so one composer install serves both.
docker exec npv3-nexgen-backend composer install --no-interaction --prefer-dist
for c in npv3-nexgen-backend npv3-nexgen-free-backend; do docker exec "$c" bash -c 'mkdir -p storage/logs storage/framework/sessions storage/framework/views storage/framework/cache bootstrap/cache && chown -R www-data:www-data storage bootstrap/cache && chmod -R 775 storage bootstrap/cache'; done
docker exec npv3-nexgen-backend php artisan key:generate --force
docker exec npv3-nexgen-free-backend php artisan key:generate --force
5. Run the migrations and seeders
docker exec npv3-nexgen-backend php artisan migrate --force
docker exec npv3-nexgen-free-backend php artisan migrate --force
The mcp-server and the api service each update one column, on a table that exists only after the migrations. Grant that on the native MySQL:
mysql -u root -e "GRANT UPDATE (last_used_at) ON nexgen_v3.personal_access_tokens TO 'mcp_readonly'@'%'; GRANT UPDATE (last_used_at) ON nexgen_free_v3.personal_access_tokens TO 'mcp_readonly'@'%'; GRANT UPDATE (last_used_at) ON nexgen_v3.personal_access_tokens TO 'flask_readonly'@'%'; FLUSH PRIVILEGES;"
Then seed both databases:
docker exec npv3-nexgen-backend php artisan db:seed --force
docker exec npv3-nexgen-free-backend php artisan db:seed --force
6. Start the V2 apps (optional)
The V2 apps use the same databases.
cd ~/git/axxya/nexgen_v2
docker compose up -d
Local URLs
http://localhost:4200is the paid app frontend (nexgen-frontend).http://localhost:4201is the trial app frontend (nexgen-free-frontend).http://localhost:8010is the paid app backend (nexgen-backend).http://localhost:8011is the trial app backend (nexgen-free-backend).http://localhost:5050is the api service. The customer API base URL ishttp://localhost:5050/v3/customer.http://localhost:3001is the mcp-server.http://localhost:8025is Mailpit, which shows every email the V3 apps send.http://localhost:8001,http://localhost:8002, andhttp://localhost:8004are the V2 nexgen, nexgen-free, and nexgen-api services. The V2 Mailpit is athttp://localhost:8026.
The seeders create these logins, among others:
admintest@axxya.com/adminpass@word1, an admin user.test@axxya.com/testpass@word1, a client user.dev@axxya.com/devpass@word1, a client user.
The frontends take a few minutes to compile after the containers start. docker logs -f npv3-nexgen-frontend shows the progress.
Day-to-day commands
docker compose up -d nexgen-backend nexgen-free-backend nexgen-frontend nexgen-free-frontend mcp-server api mailpit
docker compose stop
docker compose ps
brew services list
brew services restart mysql@8.4
brew services restart mongodb-community@8.0
docker compose down removes the containers. Use docker compose stop to turn the stack off.
After pulling changes, run the migrations again on both backends, and the seeders if the pull changed them. After a seeder changes roles or permissions, clear the view cache with docker exec npv3-nexgen-backend php artisan view:clear, then log out and back in.
The Laravel log is at storage/logs/ inside each backend container, which is nexgen/backend/storage/logs/ on the Mac.
Running the database containers again
The mysql and mongo containers and their volumes stay defined. To run them on purpose, for example to compare against MongoDB 5.0:
docker compose --profile docker-dbs up -d mysql mongo
docker compose stop mysql mongo
They listen on host ports 3307 and 27018, so they do not collide with the native servers. The backends keep using whichever host their environment files name.
Troubleshooting
-
A backend cannot connect to MySQL or MongoDB. Check
brew services listshows both servers as started, and thatDB_HOSTandMONGO_DB_HOSTarehost.docker.internalin both environment files. A changed environment file needsdocker compose up -d <service>to recreate the container. -
V2 fails with "authentication method unknown to the client". The two authentication lines are missing from
my.cnf, or MySQL was not restarted after adding them. -
A page still behaves as it did before you pulled a change to routes, service providers or Blade views. The running backend keeps its compiled routes, config and views, and PHP's opcode cache, until they are cleared and the container restarts. Run this for both backends:
docker compose exec nexgen-backend sh -c 'php artisan route:clear && php artisan config:clear && php artisan view:clear'docker compose exec nexgen-free-backend sh -c 'php artisan route:clear && php artisan config:clear && php artisan view:clear'docker compose restart nexgen-backend nexgen-free-backendNormal CI deployments use a clean checkout and do not generate route caches, so a deployed backend loads the new routes when its container starts. A manual
./deploy.shorcopilot svc deploybuilds the image from your working tree; the root.dockerignoreleaves outbootstrap/cache/routes-*.php,bootstrap/cache/config.phpand compiled views, so a cache generated locally does not reach the image. -
A backend test fails now and then with
Duplicate entry '<id>' for key 'users.PRIMARY'or'clients.PRIMARY'. Some PHPUnit fixture helpers pick a random id from a few hundred values. If one test calls such a helper twice, the two calls can draw the same id, about once in a few hundred runs, and a rerun passes. The helper must draw again while the id is already taken:do {$userId = 999200 + random_int(0, 500);} while (DB::table('users')->where('id', $userId)->exists());The same error on every run, or in a file whose tests each create one user, points instead at rows left behind by another test file. The
testerskill innexgen_v3covers that case. -
A query is fast locally and slow on staging. A local MongoDB does not reproduce DocumentDB's query planner. See Nexgen V3 databases.
Related
- Running multiple local environments for giving git worktrees their own databases, containers, and ports.
- Nexgen V3 databases for the staging and production databases.
- Nexgen V3 deployment for deploying to staging and production.
- Nexgen V3 feature flags for the flags that change what the apps show.