Skip to the article
Aniq-UI

NorthstackTroubleshooting

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

What you see
failed to connect to the docker API at unix:///…/docker.sock; check if the path is correct and if the daemon is running

Older Docker versions say "Cannot connect to the Docker daemon" instead. Either way, the Docker engine is not started.

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

  2. Check that Docker answers

    Terminal
    docker info

    Expected result: It prints a Server section instead of an error.

  3. Run your start command again

    docker compose up --build for the full stack, or your docker build and docker run for a single app.

A port is already in use

What you see
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 :::3031

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

  1. Find what holds the port

    On macOS or Linux, with the port from the message:

    Terminal
    lsof -i :3031

    On Windows, in PowerShell:

    Terminal
    netstat -ano | findstr :3031
  2. Stop it

    Close that program, or stop the earlier run: Ctrl+C in its terminal, docker compose down in its folder, or docker stop for a container you started with docker run. Then start Northstack again.

  3. Or run Northstack on other ports

    With the full stack in Docker, create a file named .env in the northstack-full-stack folder, beside docker-compose.yml, with the port you need. SITE_PORT moves the website, ADMIN_PORT the dashboard and API_PORT the API; the addresses the apps use, CORS_ORIGIN included, follow by themselves.

    northstack-full-stack/.env
    ADMIN_PORT=3041

    Then 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 .env as well.

    Terminalin northstack-full-stack
    docker compose up --build

    Expected 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).

Configuration

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.
Terminalin northstack-full-stack
docker compose build --no-cache
docker compose up

For 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

What you see
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.

Terminal
corepack enable

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

Terminal
node -v

If 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

What you see
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:

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

Terminal
yarn seed
yarn dev

It 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

What you see
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 seed in 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_LOGIN in the API's .env changes 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 answers 401 with 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 seed for the sample accounts, or yarn admin:create for 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_LOGIN changes the number.

A Sample data notice is at the bottom of every page

What you see
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_URL empty, or with no .env at all. That is how a single frontend package runs on its own. To use an API, set it in .env to the API's address with /api (http://localhost:8000/api, as in .env.example), then restart yarn dev or 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"

What you see
Access to XMLHttpRequest at 'http://localhost:8000/api/…' from origin 'http://localhost:3041' has been blocked by CORS policy

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

The API's .env
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_URL is the dashboard's address, which its live permission updates connect from, and DASHBOARD_URL the one written into invitation and reset links. Move both with the dashboard; move WEBSITE_URL with 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_URL and ADMIN_URL in the .env beside docker-compose.yml set 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_URL names 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:

Terminal
docker run -p 3030:3030 -e API_INTERNAL_URL=http://host.docker.internal:8000/api --add-host=host.docker.internal:host-gateway northstack-website

The 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:

What you see
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 api in 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 logs with the container's id (docker ps lists it).
  • To send real email, set SMTP_HOST and the other SMTP_* values, then restart the API. A message the server refuses is logged as Could not send with 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_KEY is set (or always, with BILLING_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_KEY is set, and PayPal once PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET are.
  • 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/stripe confirms 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_SECRET is empty or wrong. The API's log says STRIPE_WEBHOOK_SECRET is empty when 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.

Terminal
yarn build
yarn schema:prod

With 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 dev again.
  • With `yarn build` and `yarn start`: run yarn build again.
  • With Docker Compose: put the value in the .env beside docker-compose.yml (the ports and SITE_URL, ADMIN_URL, API_URL), not in the app's folder, and run docker 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

What you see
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_KEY or DEEPSEEK_API_KEY to the API's .env and restart the API.
  • With Docker Compose, copy back-end/.env.example to back-end/.env first, add the key, then run docker compose up --build again.
  • 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.
What you see
… 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

What you see
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.

Terminal
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

What you see
caution: filename not matched:  …zip

This 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 unzip is 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, with back-end, dashboard, website and docker-compose.yml.
  • The single apps extract to northstack-website, northstack-dashboard and northstack-backend, each with its own Dockerfile.

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:

Terminal
docker compose down -v
docker compose up

With the API container alone, remove the container (docker rm -f with its name), then the volume, and run it again:

Terminal
docker volume rm northstack-api-data
docker run -p 8000:8000 -v northstack-api-data:/data northstack-api

Without Docker, stop the API and run this in its folder (in PowerShell, Remove-Item database.sqlite*; yarn seed):

Terminal
rm -f database.sqlite* && yarn seed

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

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.