How to get the stack running locally
Source:
g2labs-in/billuu-docs@7780d43·content/start/how-to-get-running.md· updated 2026-09-26
What you end up with
Four processes. Two you run with Node, two you run with Docker.
| Piece | Repo | Command | Address |
|---|---|---|---|
| API | brewlabs-billu-backend | npm run dev | http://localhost:8000 |
| Web app | brewlabs-billu-frontend | npm run dev | http://localhost:5173 |
| PostgreSQL 15 | none, a container | docker run | localhost:5432 |
| Redis 7 | none, a container | docker run | localhost:6379 |
Redis is optional. The browser flow works without it.
Steps 3 to 8 were run end to end against the current main of the backend and the current new_design_implentation of the frontend, on Linux with Node 25 and Docker 29. Where another document describes a step that does not work, this page says so and gives the command that does.
Prerequisites
| Thing | Version | Where that is pinned |
|---|---|---|
| Node.js | 20 | .github/workflows/ci.yml uses node-version: '20', Dockerfile.dev uses node:20-alpine |
| Node.js | 22 or newer | CONTRIBUTING.md setup section |
| npm | ships with Node | npm ci reads package-lock.json in both repos |
| Docker with Compose | recent | only for Postgres and Redis |
| git | any | access to both g2labs-in repos |
Nothing enforces the Node version. Neither repo has an .nvmrc and neither package.json declares engines. Use 20 to match CI, or 22 or newer to match CONTRIBUTING.md. Install it with nvm or mise if your machine defaults to something newer.
Ports 8000, 5173, 5432 and 6379 must be free.
Step 1. Clone the two repos side by side
mkdir -p ~/code && cd ~/code
git clone https://github.com/g2labs-in/brewlabs-billu-backend.gitgit clone https://github.com/g2labs-in/brewlabs-billu-frontend.gitgit clone https://github.com/g2labs-in/brewlabs-billu-admin.git breww-adminUse the SSH URLs (git@github.com:g2labs-in/...) if your key is set up.
The backend stays on main. The frontend does not.
cd brewlabs-billu-frontendgit checkout new_design_implentationcd ..new_design_implentation is the frontend branch the work is on. It is 18 commits ahead of the frontend’s main, it is the branch that carries the frontend CI workflow, and docs/LOCAL_SETUP.md in the backend repo names it as the branch to test against.
Step 2. Install dependencies
cd ~/code/brewlabs-billu-backend && npm cicd ~/code/brewlabs-billu-frontend && npm cinpm ci wipes and reinstalls node_modules from the lockfile. The two trees are roughly 225 MB and 860 MB.
Step 3. Start Postgres and Redis
CONTRIBUTING.md and README.md both tell you to run docker compose up -d postgres redis from the backend repo. That command fails.
$ docker compose up -d postgres redisno such service: postgresThe backend’s own docker-compose.yml declares api, db, pgbouncer and redis. There is no postgres service. The db service also publishes no host port, so an API running on your host cannot connect to it even if you start it. That file describes the deployed stack, not a local dependency.
Two commands that do work.
docker run -d --name billu-postgres -p 5432:5432 \ -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=brewwlabs postgres:15-alpine
docker run -d --name billu-redis -p 6379:6379 redis:7-alpinePostgres prints rejecting connections while it initialises and then accepting connections. Check it with:
docker exec billu-postgres pg_isready -U postgres -d brewwlabsThe user, password, port and database name above match .env.example exactly, so step 4 needs no database edits. POSTGRES_USER is not set, which gives you postgres.
The workspace directory that holds the clones has a second option. It carries a Makefile, a docker-compose.yml and a .env.example of its own, and docker compose up -d postgres redis works there. Be aware of two things. Those three files are not in any repo, so a fresh clone will not have them. That postgres service creates a database named control_plane, so you have to point DATABASE_URL and CONTROL_PLANE_DATABASE_URL at localhost:5432/control_plane instead of brewwlabs.
Step 4. Create the backend environment file
cd ~/code/brewlabs-billu-backendcp .env.example .env.gitignore tracks .env.example and ignores .env and .env.*. Never commit a real file.
Four things in .env matter for a local run.
| Variable | Value | Why |
|---|---|---|
DATABASE_URL | postgresql://postgres:postgres@localhost:5432/brewwlabs | already the value in .env.example, and the value the container in step 3 accepts |
CONTROL_PLANE_DATABASE_URL | the same value | the control plane lives in the public schema of the same database |
ENCRYPTION_KEY | 64 hex characters | missing from .env.example. Step 5 fails without it |
JWT_SECRET, REFRESH_TOKEN_SECRET | any string locally | token signing |
The exact failure without ENCRYPTION_KEY:
❌ Missing environment variables: ENCRYPTION_KEYsrc/scripts/initialize_control_plane.js requires it and checks the format. Use the dev-only key that .env.example already carries for CONTROL_PLANE_KMS_KEY:
ENCRYPTION_KEY=62e9e04562382067c920181ba498f5978ed9b9687903957e8e900981d137cd95Leave the optional integrations empty. Razorpay, Cloudinary, Firebase, WhatsApp, email and Google OAuth have no local values, and the features that need them fail on their own. NODE_ENV=development, PORT=8000 and FRONTEND_URL=http://localhost:5173 are already in .env.example and should stay as they are.
Two details about how the file is read.
app.jsloads.envat the top and then forcesNODE_ENV=productionif it is unset. KeepNODE_ENVin the file.- A few scripts load
.env.localinstead, for examplesrc/scripts/check-associations.jsandsrc/scripts/resetPassword.js. Create.env.localonly if you run those.
The frontend needs no environment file. vite.config.js proxies /api to process.env.VITE_API_PROXY_TARGET or to http://localhost:8000.
Step 5. Create the schema and seed data
npm run migrate:control-planenpm run seed:control-planenode src/scripts/init-database.js --wipe| Command | Runs | Result |
|---|---|---|
npm run migrate:control-plane | src/scripts/initialize_control_plane.js | creates the public control plane tables, about 2 seconds |
npm run seed:control-plane | src/scripts/create-superadmin.js | creates admin@brewwlabs.com with password Admin@123456 |
node src/scripts/init-database.js --wipe | src/scripts/init-database.js | creates the demo tenant, 10 products, 6 variants, 15 orders, and the login below |
The demo login, printed by the seed script:
| Role | Password | |
|---|---|---|
| Tenant owner | demo@brewlabs.com | Demo@123 |
| Super admin | admin@brewwlabs.com | Admin@123456 |
init-database.js also creates the control plane tables when they are missing, so on an empty database that one command is enough for the demo tenant. It does not create a super admin. Keep npm run seed:control-plane in the sequence if you want the admin account.
--wipe drops and rebuilds the demo tenant schema. Point it only at your own database. docs/LOCAL_SETUP.md warns about --wipe because that guide runs against the shared Supabase test instance. The warning does not apply to the local container from step 3.
The super admin is not a login for the web app. src/App.jsx signs out any session with role === "SUPER_ADMIN".
Step 6. Start the backend
npm run devThat is nodemon --inspect=0.0.0.0:9229 --exec 'npm run start', which runs node app.js. npm start runs the same thing without nodemon.
The entry point is app.js at the repo root. package.json says "main": "src/app.js" and that entry is wrong.
Confirm the API and its database:
curl -s localhost:8000/health{"success":true,"message":"Success","data":{"db":"ok","redis":"down","timestamp":"...","uptime":8.6,"environment":"development","version":"3.1.0-security-audit-cors-fixed","updatedAt":"2026-04-04 23:05","trustProxy":"enabled"}}db must be ok. redis is down until step 8, and that is the expected value with the example .env.
Step 7. Start the frontend and log in
cd ~/code/brewlabs-billu-frontendnpm run devVite serves http://localhost:5173 and proxies /api to the backend, so there is no CORS setup to do and no env file to write.
Open http://localhost:5173. The app lands on /auth. Log in with demo@brewlabs.com and Demo@123. You should land on the dashboard with the seeded outlet, showing today’s revenue, active orders and table occupancy.
Two checks that need no browser:
curl -s localhost:5173 | grep -o "<title>.*</title>"That prints <title>BrewwLabs</title>.
curl -s -X POST localhost:8000/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"identifier":"demo@brewlabs.com","password":"Demo@123"}'That returns {"success":true,"message":"Success","data":{"user":{...},"organization":{...}}}.
The login field on the screen and in the API is identifier, and it accepts an email or a phone number. The panel type comes from the token, not from a request header, so a scripted call needs no x-panel-type header. src/core/middleware/tokenVerification.js derives it from the role, and src/config/corsConfig.js allows the header for the browser client, which sends it.
Step 8. Optional, turn Redis on
Redis is off by default. src/config/redis.js treats it as disabled unless REDIS_ENABLED is exactly the string true, and .env.example does not set it. In that state the cache layer is an in-process mock and the BullMQ workers do not start.
With the container from step 3 running, add these to .env:
REDIS_ENABLED=trueREDIS_URL=redis://localhost:6379Restart the backend. /health then reports "redis":"ok".
Step 9. Optional, run everything in containers
The workspace directory that holds the clones has a Makefile that drives the whole stack and the two repos together through docker-compose.yml.
| Target | Does |
|---|---|
make init | copies .env.example to .env, then builds images |
make up | docker compose up -d |
make up-logs | starts in the foreground and follows the logs |
make down | stops everything |
make ps | shows the containers |
make logs, make logs-backend, make logs-frontend, make logs-db, make logs-redis | follow logs |
make restart-backend, make restart-frontend | restarts one service |
make shell-backend, make shell-frontend, make shell-db | shells into a container |
make migrate | npm run migrate:control-plane inside the backend container |
make migrate-status | npm run df:check-migrations |
make seed | npm run seed:control-plane |
make db-reset, make db-backup, make db-restore FILE=... | database maintenance |
make test, make test-unit, make test-integration, make test-security | Jest suites, see the warning below |
make lint, make lint-fix | eslint, see the warning below |
make health | pings the four services |
make clean, make clean-all | removes Docker resources |
Two caveats. make init copies .env.example to .env at the workspace root, which is a different file from the backend’s .env, and the root file points DATABASE_URL at the postgres service name. That compose file also mounts brewlabs-billu-backend/migrations into the Postgres data directory for first boot initialisation, and main has no tracked migrations/ directory, so the first boot applies no schema. Run make migrate after make up. And make health looks for a status field that /health does not return, so the backend line reports UNREACHABLE while the API is healthy. Use curl localhost:8000/health.
Confirm it worked
| Check | Command | Expected |
|---|---|---|
| Database container | docker exec billu-postgres pg_isready -U postgres -d brewwlabs | accepting connections |
| Schema and seed | npm run df:health in the backend | runs src/scripts/data-first-cli.js health and prints a health table for the database |
| API health | curl -s localhost:8000/health | "db":"ok" |
| Web app | curl -s -o /dev/null -w '%{http_code}\n' localhost:5173 | 200 |
| Login | step 7, in a browser | the dashboard, with seeded orders |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
❌ Missing environment variables: ENCRYPTION_KEY from npm run migrate:control-plane | .env.example has no ENCRYPTION_KEY | add a 64 hex character ENCRYPTION_KEY to .env, step 4 |
no such service: postgres from docker compose up -d postgres redis | the backend compose file has db, not postgres | use the docker run commands in step 3, or run compose from the workspace root |
"db":"down" in /health | the API cannot reach Postgres | check the container is up and DATABASE_URL names localhost:5432/brewwlabs |
"redis":"down" in /health | Redis is disabled by default | expected. Set REDIS_ENABLED=true, step 8 |
| Login fails with a network error in the browser | the API is not running, or a local frontend env file sets VITE_API_BASE_URL, which src/auth/authService.js and several components read | start the backend, and remove VITE_API_BASE_URL from any local frontend env file. The dev server proxies /api to localhost:8000 on its own |
EADDRINUSE on 8000 or 5173 | something else holds the port | find it with ss -ltnp | grep 8000 and stop it |
npm run lint fails in the backend | eslint is not a dependency of the backend | known gap, recorded in CONTRIBUTING.md. Use npm run check:syntax |
npm run test:unit, test:integration, test:security or test:regression fails | those scripts point at a tests/ directory that is not in the repo | use the smoke chain instead, in the working agreements page |
Refusing to run: DATABASE_URL host is "unset", not localhost from a smoke script | the smoke harness reads the environment, not .env, and refuses anything that is not local | export DATABASE_URL first, for example export DATABASE_URL=postgresql://postgres:postgres@localhost:5432/brewwlabs |
| The superadmin cannot log into the web app | the web app is the tenant panel | use demo@brewlabs.com, the superadmin is for the admin API |
Red errors about audit_logs in the backend log right after a login | a database seeded by init-database.js --wipe is missing columns the audit model writes | the login still succeeds and the API answers 200. The failed write is the audit row |
Node version complaints during npm ci | the shell is on a different Node | nvm use 20, then npm ci again |
Sources
brewlabs-billu-backend/CONTRIBUTING.mdbrewlabs-billu-backend/README.mdbrewlabs-billu-backend/AGENTS.mdbrewlabs-billu-backend/docs/LOCAL_SETUP.mdbrewlabs-billu-backend/package.jsonbrewlabs-billu-backend/.env.examplebrewlabs-billu-backend/.gitignorebrewlabs-billu-backend/app.jsbrewlabs-billu-backend/docker-compose.ymlbrewlabs-billu-backend/Dockerfile.devbrewlabs-billu-backend/src/config/redis.jsbrewlabs-billu-backend/src/config/corsConfig.jsbrewlabs-billu-backend/src/core/middleware/tokenVerification.jsbrewlabs-billu-backend/src/scripts/initialize_control_plane.jsbrewlabs-billu-backend/src/scripts/create-superadmin.jsbrewlabs-billu-backend/src/scripts/init-database.jsbrewlabs-billu-backend/scripts/test-api-smoke.jsbrewlabs-billu-backend/scripts/lib/smoke-harness.jsbrewlabs-billu-backend/scripts/test-bootstrap.jsbrewlabs-billu-backend/.github/workflows/ci.ymlbrewlabs-billu-frontend/LOCAL_SETUP.mdbrewlabs-billu-frontend/package.jsonbrewlabs-billu-frontend/vite.config.jsbrewlabs-billu-frontend/playwright.config.jsbrewlabs-billu-frontend/src/App.jsxbrewlabs-billu-frontend/src/auth/authService.jsbrewlabs-billu-frontend/Dockerfile.dev- workspace root
Makefile - workspace root
docker-compose.yml - workspace root
.env.example