Troubleshooting
The errors you can meet while installing Northstack, what causes each one, and how to fix it.
For the Full Stack package
Docker is not running
failed to connect to the docker API at unix:///…/docker.sock; check if the path is correct and if the daemon is runningOlder Docker versions say "Cannot connect to the Docker daemon" instead. Either way, the Docker engine is not started.
Open Docker Desktop
Start Docker Desktop and wait until it reports that the engine is running. On Linux, start the service:
sudo systemctl start docker.Check that Docker answers
Terminaldocker infoExpected result: It prints a Server section instead of an error.
Run your start command again
docker compose up --buildfor the full stack, or yourdocker buildanddocker runfor a single app.
A port is already in use
ports are not available: exposing port TCP 0.0.0.0:3031 … bind: address already in use
Bind for 0.0.0.0:3031 failed: port is already allocated
Error: listen EADDRINUSE: address already in use :::3031The first two lines come from Docker, the last from yarn dev. Another program already listens on 3030, 3031 or 8000: often an earlier run of Northstack, or another project's dev server.
Find what holds the port
On macOS or Linux, with the port from the message:
Terminallsof -i :3031On Windows, in PowerShell:
Terminalnetstat -ano | findstr :3031Stop it
Close that program, or stop the earlier run: Ctrl+C in its terminal,
docker compose downin its folder, ordocker stopfor a container you started withdocker run. Then start Northstack again.Or run Northstack on other ports
With the full stack in Docker, create a file named
.envin thenorthstack-full-stackfolder, besidedocker-compose.yml, with the port you need.SITE_PORTmoves the website,ADMIN_PORTthe dashboard andAPI_PORTthe API; the addresses the apps use,CORS_ORIGINincluded, follow by themselves.northstack-full-stack/.envADMIN_PORT=3041Then run the same command again. A failed start picks up where it stopped. Docker names one busy port at a time: if it stops on another one, add that one to
.envas well.Terminalinnorthstack-full-stackdocker compose up --buildExpected result: The app answers on its new port, here localhost:3041Local.
For a single app started with docker run, change the number on the left of -p, for example -p 3041:3031. A frontend that talks to an API is also built with its new address (NEXT_PUBLIC_BASE_URL for the dashboard, NEXT_PUBLIC_SITE_URL for the website), and that address goes into the API's CORS_ORIGIN, and its DASHBOARD_URL and FRONTEND_URL (the dashboard) or WEBSITE_URL (the website). Without Docker, the website's port is in its package.json scripts (yarn next dev -p 3040 for another), the dashboard's is PORT in its .env, and the API's is PORT in its .env. Move the API together with PUBLIC_API_URL, PUBLIC_MEDIA_URL and both frontends' NEXT_PUBLIC_API_BASE_URL (the dashboard's NEXT_PUBLIC_WEBSOCKET_BASE_URL too).
The Docker build fails
The first build downloads the base images and every dependency, then builds the images. It stops with "failed to solve" and the step that failed when something gets in the way.
- No connection or a timeout: the build needs internet access. Run the command again once you are online; finished steps are cached.
- No space left on device: free space in Docker Desktop, or check what Docker uses with
docker system df. - It fails at the same step every time: rebuild without the cache, then start.
- Compose rejects `docker-compose.yml` itself, naming `env_file`: your Compose is older than 2.24. Update Docker Desktop, or the Compose plugin on Linux.
northstack-full-stackdocker compose build --no-cache
docker compose upFor a single-app package, add --no-cache to your docker build command.
A first start that seems stuck is usually still seeding the sample workspaces and the 732 organizations behind the platform figures. The website and the dashboard start only once the API reports itself healthy.
Yarn says its version is 1.22
This project's package.json defines "packageManager": "yarn@4…". However the current global version of Yarn is 1.22…Each app pins Yarn 4.8.1 through Corepack. This message means Corepack is not enabled yet, so the old global Yarn answered instead.
corepack enableThen run yarn install again. If your Node.js has no Corepack (Node.js 25 and newer leave it out), install it first with npm install -g corepack. npm and pnpm are not used: each app ships its own yarn.lock.
An install or a start fails on an older Node.js
The API needs Node.js 22.13 or a later 22 release, or Node.js 24.11 or newer; the website and the dashboard on their own need Node.js 22.9 or newer. The Docker images run on Node.js 24. An older Node.js shows up as an install that fails to build a package, or an app that stops at start.
node -vIf it prints an older version, install Node.js 24 (or a 22 release from 22.13), run corepack enable again (on Node.js 25 and newer, npm install -g corepack first), then delete the app's node_modules folder and run yarn install again. Node.js 23, and 24 releases before 24.11, are outside the API's range too. With Docker none of this applies: the images bring their own Node.js.
The API stops before starting
The API cannot start: JWT_SECRET is not set. Set it in back-end/.env to the output of: …
The API cannot start: JWT_SECRET is still the example value from .env.example, so anyone can sign a token for any account. …
The API cannot start: CORS_ORIGIN is not set. List the website and dashboard origins, comma separated, …The API checks its settings before it starts and prints one line per problem. The causes:
- There is no `.env` file, or `JWT_SECRET` is empty. Create the file in the API's folder with
cp .env.example .env. - `JWT_SECRET` is still the example value with `NODE_ENV=production`. Anyone could sign a token with the public example, so a production start refuses it. Generate your own secret.
- `CORS_ORIGIN` is empty with `NODE_ENV=production`. List the website's and the dashboard's addresses, comma separated.
Generate a secret with:
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"With Docker you do not need one: a container without a JWT_SECRET of its own, or with the example one, generates a secret and keeps it in the data volume.
Nobody can sign in on a new database
Without Docker, yarn dev with NODE_ENV=development creates the tables on its first start, but no account: the accounts and the sample workspaces come from yarn seed. Stop the API, run it in the API's folder, and start it again.
yarn seed
yarn devIt ends with Seed complete. and the accounts to sign in with. On failure it prints Seeding failed: followed by the reason, such as a MySQL database that does not exist yet. The Docker image seeds a new SQLite volume by itself; on MySQL, run node dist/database/seeder.js once in the container.
A database started empty (yarn schema, or Docker with SEED_SAMPLE_DATA=false and no ADMIN_EMAIL and ADMIN_PASSWORD) has no account either, on purpose. Create your first Platform admin with yarn admin:create (built: yarn admin:create:prod; Docker: docker compose exec -u node api node dist/database/create-admin.js), which asks for the email and the sign-in details.
Sign-in fails
That email and password do not match an account. Try again.
Too many failed sign-in attempts. Wait 15 minutes, then try again.- Check the account. Every account signs in on the dashboard, at port
3031; the website has no sign-in of its own, and its Live demo buttons open the dashboard's sign-in. The seeded accounts and their password are on the installation page. - The same message answers a wrong password, an unknown email and a database that was never seeded. Check what you typed; on a new database,
yarn seedin the API's folder creates the accounts. - "Too many failed sign-in attempts. Wait 15 minutes, then try again." Ten failed sign-ins from one address within 15 minutes refuse every further try from it, with the right password too, until the oldest failure is 15 minutes old. Wait, or restart the API: the count is kept in its memory.
RATE_LIMIT_LOGINin the API's.envchanges the number. - "New sign-ups are closed on this platform right now." on the sign-up page or in onboarding: platform staff have switched New sign-ups off in the platform settings. Existing accounts still sign in.
- You changed a password and no longer have it: start over with fresh demo data, below.
- Signed in, but the page shows the Sample data notice: the dashboard could not reach its API and answered from its sample data. See the next problem.
Sign-in fails
- Check the account and the route. Every account signs in at
POST /api/auth/login; the seeded accounts and their password are on the installation page. A failed sign-in answers401with the same message whether the email or the password is wrong. - No account works at all: the database was created without the sample data. Run
yarn seedfor the sample accounts, oryarn admin:createfor your own Platform admin, as in the problem above. - A `429` answer means one address failed 10 sign-ins within 15 minutes. It clears once the oldest failure is 15 minutes old, or when the API restarts.
RATE_LIMIT_LOGINchanges the number.
A Sample data notice is at the bottom of every page
No API is connected, so this dashboard runs on built-in sample data and your changes stay in this browser. …
The API at http://localhost:8000/api is not answering, so this dashboard is showing built-in sample data, not your workspace's. …The website says the same in its own words. Each frontend carries built-in sample data and answers from it in two cases:
- "No API is connected": the app was started or built with
NEXT_PUBLIC_API_BASE_URLempty, or with no.envat all. That is how a single frontend package runs on its own. To use an API, set it in.envto the API's address with/api(http://localhost:8000/api, as in.env.example), then restartyarn devor build again; with Docker, build without--build-arg NEXT_PUBLIC_API_BASE_URL=. - "The API at … is not answering": an address is set, but the browser cannot reach it. The API is not running, runs at another address, or does not list this app's address in its
CORS_ORIGIN(see the next problem). Check that localhost:8000/api/ healthLocal answers, then reload the page. The app tries the API again by itself.
While the sample data stands in, what you change is kept in that browser only, and in the dashboard any password signs in. The browser keeps trying the address meanwhile and logs each refused request in its console, so leave the address empty when there is no API. The website shows the notice in yarn dev, and in a production build only when it was built with NEXT_PUBLIC_SAMPLE_DATA_NOTICE=always (--build-arg NEXT_PUBLIC_SAMPLE_DATA_NOTICE=always with Docker); the pages its server renders never fall back.
The browser says "blocked by CORS policy"
Access to XMLHttpRequest at 'http://localhost:8000/api/…' from origin 'http://localhost:3041' has been blocked by CORS policyThe browser's console shows this line when a frontend runs on an address the API does not accept. A refused request counts as an API that does not answer, so the frontend stands in with its sample data in the browser: the dashboard shows the Sample data notice, and the website does too in yarn dev. The API answers browsers only from the addresses in CORS_ORIGIN, which defaults to http://localhost:3030 and http://localhost:3031. A website or dashboard moved to another port, or served on your own domain, is refused until it is listed.
CORS_ORIGIN=http://localhost:3030,http://localhost:3041
FRONTEND_URL=http://localhost:3041
DASHBOARD_URL=http://localhost:3041- Write each address exactly as the browser shows it, with the scheme and the port and without a trailing slash, comma separated. Then restart the API.
FRONTEND_URLis the dashboard's address, which its live permission updates connect from, andDASHBOARD_URLthe one written into invitation and reset links. Move both with the dashboard; moveWEBSITE_URLwith the website.- For an API container, pass the same values with
-e, such as-e CORS_ORIGIN=http://localhost:3030,http://localhost:3041. - With the full stack in Docker you do not edit these:
SITE_PORT,ADMIN_PORT,SITE_URLandADMIN_URLin the.envbesidedocker-compose.ymlset them. - A frontend that cannot reach the API at all, because it is stopped or at another address, fails the same way without the CORS line. Check that localhost:8000/
api/ healthLocal answers, and that the frontend's NEXT_PUBLIC_API_BASE_URLnames that API.
The checkout's receipt loads late in Docker
The website's own server reads the checkout's options and an order's receipt while it renders those pages. Inside a container localhost is the container itself, so that server cannot reach an API on your computer at http://localhost:8000. The pages still complete in the browser, which reaches the API directly.
Start the website container with the API's address as the server sees it:
docker run -p 3030:3030 -e API_INTERNAL_URL=http://host.docker.internal:8000/api --add-host=host.docker.internal:host-gateway northstack-websiteThe full stack in Docker sets it for you (http://api:8000/api). Without Docker it is not needed.
An invitation or a reset email never arrives
Until SMTP_HOST is set in the API's .env, no email is sent. The API says so when it starts, and every message (invitations, weekly digests, password resets, licence receipts, billing emails and forwarded contact messages) is written to its log instead, links included:
Mail is not set up (SMTP_HOST is empty): invitations, weekly digests, billing emails, password resets, licence receipts and forwarded contact messages are written to this log instead of being sent.
Message to …: …- Full stack in Docker: run
docker compose logs apiin the package folder and open the link from the newest message. - Without Docker: read it in the terminal that runs the API's
yarn dev. - API container:
docker logswith the container's id (docker pslists it). - To send real email, set
SMTP_HOSTand the otherSMTP_*values, then restart the API. A message the server refuses is logged asCould not sendwith the reason. - An invitation lasts seven days; after that its page says "This invitation has expired." Send a new one from the Team page.
- No billing email arrives (a failed payment, an ended trial, a plan change): they go to the workspace's owner, its billing members and its billing email only, and only while Email notifications is on in the workspace's Settings, Notifications.
- No weekly digest yet: it goes out on Mondays from 08:00 UTC, while Weekly digest is on. A new install's first run only marks the current week, and a new workspace's first digest covers its first full week.
A checkout opens the test checkout
Both payment flows open a built-in test checkout while the API has no keys for them, and neither charges anybody:
- Change plan on the dashboard's Billing page opens the Test checkout page, "Confirm the … plan", until
STRIPE_SECRET_KEYis set (or always, withBILLING_PROVIDER=simulator). Manage payment method then says "The test checkout keeps no card, so there is no payment method to manage." - The website's licence checkout says "Test mode: payments go through this site's test checkout and charge nobody." and opens its own Test checkout page. Card uses Stripe once
STRIPE_SECRET_KEYis set, and PayPal oncePAYPAL_CLIENT_IDandPAYPAL_CLIENT_SECRETare. - A Stripe test key shows "Test mode: no real money moves." On Stripe's page, pay with the card
4242 4242 4242 4242, any future date and any CVC. - "Confirming the payment..." or a receipt that says the payment is still being confirmed: the API asks Stripe for the session while the page waits, and on a server the webhook at
/api/billing/webhooks/stripeconfirms it at once. A plan changes only once the payment is confirmed. - "The checkout expired. Start again from Change plan." A checkout session that was left open too long; start a new one.
- Stripe payments go through but renewals, failed payments or cancellations never show on a server:
STRIPE_WEBHOOK_SECRETis empty or wrong. The API's log saysSTRIPE_WEBHOOK_SECRET is emptywhen it starts, and Stripe's dashboard shows each delivery refused with a 400. Set it to the signing secret of the webhook endpoint and restart the API.
Errors after an update
After you update the code on a database you keep, requests can fail with a missing table or column in the API's log, such as no such column on SQLite or Unknown column on MySQL. There are no migrations, and in production the API never changes the tables by itself, so bring them up to the new code once. Back up the database first: the update also drops a column the code no longer has.
yarn build
yarn schema:prodWith Docker, run it in the running API container: docker compose exec -u node api node dist/database/schema.js.
Everyone sees "Closed for maintenance."
The platform setting Maintenance mode is on. Until it is off, only platform staff can use the dashboard and the API answers everyone else with a 503. On the maintenance screen press Platform staff sign in, sign in as a Platform admin, and switch maintenance_mode off on the platform's Settings page. The website's contact form and licence checkout keep working meanwhile.
A change to a frontend's .env does nothing
Every NEXT_PUBLIC_* value in the website's and the dashboard's .env is compiled into the JavaScript the browser loads when the app is built, so editing the file changes nothing until the app is built again.
- With `yarn dev`: stop it and run
yarn devagain. - With `yarn build` and `yarn start`: run
yarn buildagain. - With Docker Compose: put the value in the
.envbesidedocker-compose.yml(the ports andSITE_URL,ADMIN_URL,API_URL), not in the app's folder, and rundocker compose up --build. - For a single app image: build it again with the value as a
--build-arg.
The API's own .env needs only a restart. The dashboard's PORT and the website's API_INTERNAL_URL are read when the app starts.
The assistant asks for a key, or a provider refuses
Use your own API key
The server has no key for this provider. Add your own and the assistant works fully. …The assistant answers with Google Gemini, OpenAI or DeepSeek, and the API's .env has no provider key until you add one, so nothing is billed. The API says so when it starts (No AI provider key is set), and sending a message opens Use your own API key. Either add your own key there, which stays in your browser and is used for your requests only, or give the server one:
- Add
GOOGLE_API_KEY,OPENAI_API_KEYorDEEPSEEK_API_KEYto the API's.envand restart the API. - With Docker Compose, copy
back-end/.env.exampletoback-end/.envfirst, add the key, then rundocker compose up --buildagain. - A reader's own key is used before the server's, so a reader who added one is billed on it even when the server has a key.
… has no requests left on this API key right now. Pick a different model from the list above the chat, or try again in a few minutes.That reply means the provider refused for its own limits. Pick another model in the picker, or wait and press Try again. Any other refusal, such as a key the provider did not accept, shows the provider's own message. On the sample data alone the assistant does not answer at all: it says it needs the API.
In demo mode the assistant asks for the visitor's own key
Included with your purchase. Sign in to read, or open it in your download.
Why a public demo stops paying for a visitor's assistant messages.
The contact form or the checkout says to try later
You have sent several messages in the last hour. Try again later.
Too many attempts from this address. Try again in an hour.Each address may send five contact messages and start ten licence checkouts an hour; the dashboard's sign-up allows ten an hour, and password reset requests five in 15 minutes. The API's own 429 answer names the action and the wait, such as "Too many sign-ups from this address. Wait an hour, then try again." Wait, or restart the API while testing, because the counts live in its memory. Behind a reverse proxy, set TRUST_PROXY=1 in the API's .env, or every visitor shares the proxy's one allowance; the same applies to the sign-in limit.
MySQL refuses to seed
yarn seed with DB_TYPE=mysql creates the tables in a database that already exists; it does not create the database itself. Seeding failed: followed by Unknown database means it is missing, and Access denied that DB_USERNAME or DB_PASSWORD is wrong.
mysql -u root -p -e "CREATE DATABASE northstack"Then run yarn seed again. The Docker image seeds SQLite only: with MySQL, run node dist/database/seeder.js (or node dist/database/create-admin.js to start empty) once in the API container with the same environment.
The unzip command fails or the folder looks different
caution: filename not matched: …zipThis means two ZIPs of the same package are in the folder, such as an older download beside the new one, and the * in unzip -q northstack-full-stack-*.zip matched both. Delete the older ZIP and run the command again, or type the full file name.
- If
unzipis missing, extract the ZIP in your file manager. Some tools add an extra folder named after the ZIP: move into the inner one. - The Full Stack extracts to
northstack-full-stack, withback-end,dashboard,websiteanddocker-compose.yml. - The single apps extract to
northstack-website,northstack-dashboardandnorthstack-backend, each with its ownDockerfile.
Start over with fresh demo data
This deletes your data
Everything you created (projects, members, uploads, plan changes) is gone, and the sample workspaces come back as they were seeded.
With the full stack in Docker, in the northstack-full-stack folder:
docker compose down -v
docker compose upWith the API container alone, remove the container (docker rm -f with its name), then the volume, and run it again:
docker volume rm northstack-api-data
docker run -p 8000:8000 -v northstack-api-data:/data northstack-apiWithout Docker, stop the API and run this in its folder (in PowerShell, Remove-Item database.sqlite*; yarn seed):
rm -f database.sqlite* && yarn seedOn MySQL, run yarn reset (it empties every table), then yarn seed.
Running yarn seed on a database you keep is not a reset: once the database has accounts it adds no account or workspace, and only gives the built-in platform roles their permissions back.