Back to Article List

Self-host NiubiGEO: Install on an Ubuntu 24.04 VPS with Docker

Self-host NiubiGEO: Install on an Ubuntu 24.04 VPS with Docker

NiubiGEO showed up on GitHub on September 4 and had over 2,000 stars by the time I got to it six days later, which for a tool in this category is unusual.

It's an open-source, self-hosted answer to the question every marketer has been asking since AI Overviews ate the top of the SERP: when someone asks ChatGPT or Gemini for "a VPS provider in Europe", does my brand come up, and if it doesn't, who does? And which pages are the models citing when they answer?

Ahrefs Brand Radar and Semrush's AI toolkit answer the same question for a monthly fee. NiubiGEO answers it for the price of the API calls, on your own server, with the raw model answers kept on disk so you can check every claim in the report. That last part is the reason I'm bothering with it. Every GEO dashboard I've used so far gives me a score and hides the evidence.

This guide puts NiubiGEO v0.2.0 on an Ubuntu 24.04 VPS with Docker, wires an OpenRouter key in as a read-only file, puts Caddy in front with a password and HTTPS (the app has no login of its own, and the maintainers say so), starts the optional scheduling worker and shows you how to back the data up. There's a from-source route at the end for people who'd rather not run Docker. One honest note before the commands: I ran the source build and poked at the workbench on a test box, but I haven't paid for a full audit run yet, so the cost figures below are the project's own numbers from its 20-domain study, not mine.

What you need before installing NiubiGEO

An Ubuntu 24.04 VPS with root or sudo. The app is a Node.js process that idled at about 80 MB of RAM on my box and the Docker image is built on node:22-alpine, so 2 GB of RAM is plenty; the smallest plan on our Docker VPS template does it with room to spare and comes with Docker already on it, which lets you skip step 1. You also need an OpenRouter account with some credit on it and a key from the OpenRouter keys page. v0.2.0 is OpenRouter-only: the .env.example still lists OpenAI, Anthropic, Gemini, Perplexity and DeepSeek keys, but those are leftovers from the v0.1.0 CLI and the current workbench doesn't use them. Last thing, a subdomain pointed at the server's IP, something like geo.example.com, for the Caddy step.

Step 1: Install Docker on Ubuntu 24.04

Docker's own apt repository, following Docker's Ubuntu install page. The docker.io package in Ubuntu's repos works too but lags behind.

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

docker --version should print a version number and no complaints. If you want to run docker without sudo, sudo usermod -aG docker $USER and log out and back in.

Step 2: Save the OpenRouter key as a file

The release notes tell you to export OPENROUTER_API_KEY in your shell and pass -e OPENROUTER_API_KEY to docker run. That works, and it also leaves the key in your bash history and in docker inspect output for anyone who can read it. NiubiGEO's config loader accepts OPENROUTER_API_KEY_FILE pointing at a file instead, and it trims whitespace, so a trailing newline from your editor won't break it. I use the file.

sudo mkdir -p /etc/niubigeo
sudo nano /etc/niubigeo/openrouter_api_key

Paste the key (it starts with sk-or-v1-), save, then lock it down:

sudo chmod 600 /etc/niubigeo/openrouter_api_key

The container runs as root inside, so root-only permissions on the host file are fine for the bind mount.

Step 3: Run the NiubiGEO v0.2.0 container

Pin the tag. This project shipped v0.1.0-alpha on September 4 and v0.2.0 on September 8 and the server entrypoint changed between them, so :latest is a bad idea for something you'll come back to in a month. The image is published for amd64 and arm64.

docker pull ghcr.io/albert-weasker/niubigeo:v0.2.0

docker run -d --name niubigeo \
  --restart unless-stopped \
  -p 127.0.0.1:8787:8787 \
  -e OPENROUTER_API_KEY_FILE=/run/secrets/openrouter_api_key \
  -v /etc/niubigeo/openrouter_api_key:/run/secrets/openrouter_api_key:ro \
  -v niubigeo-data:/app/data/product-v2 \
  ghcr.io/albert-weasker/niubigeo:v0.2.0

A few things in that command matter. The port is bound to 127.0.0.1, so nothing outside the box can reach the workbench until Caddy is in front of it. The key is mounted read-only at the path the env var points to. And the named volume niubigeo-data sits on /app/data/product-v2, which is where v0.2.0 keeps projects, model selections, runs and the raw answers, all as JSON files. There's no database to install.

Check it:

curl http://127.0.0.1:8787/health
docker logs niubigeo

You want {"ok": true} from the first and niubigeo product server listening on http://localhost:8787 from the second. The image also has its own healthcheck hitting /health every 30 seconds, so docker ps will show (healthy) after the first check passes.

Step 4: Caddy reverse proxy with basic auth and HTTPS

NiubiGEO has no authentication, no TLS and no user accounts. That's in docs/deployment/docker.md in as many words, and the v0.2.0 release notes repeat it: "Do not expose it publicly without access controls." Anyone who reaches port 8787 can create projects, start paid model runs on your key and read every stored answer. So the workbench goes behind a password. I'm using Caddy because it handles the certificate on its own and the whole config is six lines; if you already run nginx on the box, the nginx and HTTPS setup we use for Hermes transfers with the port changed.

Install Caddy from its official repo, per the Caddy install docs:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Generate a password hash. Caddy prompts for the password twice and prints a bcrypt string:

caddy hash-password

Now the Caddyfile. Replace the domain, the username and the hash:

sudo nano /etc/caddy/Caddyfile
geo.example.com {
    basic_auth {
        andrei $2a$14$PASTE_THE_HASH_FROM_CADDY_HERE
    }
    reverse_proxy 127.0.0.1:8787
}
sudo systemctl reload caddy

Open the firewall for 80 and 443 (Caddy needs 80 for the certificate challenge) and nothing else:

sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable

Give the DNS a minute, then open https://geo.example.com. The browser asks for the username and password, Caddy has fetched a Let's Encrypt certificate in the background, and you're looking at the NiubiGEO workbench. If Caddy logs a certificate error, the usual cause is the A record not having propagated yet, or port 80 blocked at the provider's firewall as well as ufw. Check both before you touch the config.

Step 5: Run the first domain test

Create a project with your domain. The project is saved before anything runs, no API call yet. Then pick models. The picker searches OpenRouter's catalogue, and for each model you choose separately if it runs offline or with the provider's native web search; the two give different answers and the report keeps them apart, which is the correct call. Start with one cheap model and add more once you know what you're looking for. The maintainers' own 20-domain study used openai/gpt-4o-mini and google/gemini-2.5-flash-lite offline plus openai/gpt-4.1-mini with search.

Save the configuration and run the domain test. You get, per model, how it describes your product, which competitors it names and which keywords it attaches to you and to them. Every finding has an "open the original answer" link, and that's the feature. When a model says a competitor is "cheaper", you read the sentence it said that in, not a summary of it.

Then confirm the keywords worth testing and run keyword tests. Those ask the models neutral questions ("best VPS provider for a Minecraft server", that kind of thing) without naming your brand, and record who shows up. Domain recognition tells you if the model knows you exist once prompted. Keyword tests tell you if it recommends you unprompted, and those are very different things. Their study made 204 paid calls across 20 domains, including retries and preflight, for a recorded USD 1.00 in provider costs. So a single domain with three models and a handful of keywords is cents. The thing to watch is that costLimitUsd in the measurement settings isn't a hard budget gate yet, the docs say so, so the real limit is the credit on your OpenRouter account. I'd put $10 on a dedicated key used for this and nothing else.

Step 6: Start the scheduling worker for monitoring

The HTTP server never runs anything on a schedule by itself. Scheduled monitoring is a second container, running the same image with a different command, polling the same data volume for due tasks every 60 seconds. It has no HTTP port, so the image's healthcheck has to be disabled or Docker will keep marking it unhealthy.

docker run -d --name niubigeo-worker \
  --restart unless-stopped \
  --no-healthcheck \
  -e OPENROUTER_API_KEY_FILE=/run/secrets/openrouter_api_key \
  -v /etc/niubigeo/openrouter_api_key:/run/secrets/openrouter_api_key:ro \
  -v niubigeo-data:/app/data/product-v2 \
  ghcr.io/albert-weasker/niubigeo:v0.2.0 \
  node dist/src/product/scheduling/schedule-worker.js 60

The trailing 60 is the poll interval in seconds and the minimum is 10. Don't start the worker before you've looked at which monitoring tasks are active in the workbench, because it will execute every due task it finds and each one costs API money. To stop monitoring, pause the task in the UI first, wait for any in-flight run to finish, then docker stop niubigeo-worker. Stopping the container mid-run doesn't cancel the model request that's already out the door.

Small digression. The worker and the server must mount the same volume, and the docs are strict about not giving them two different copies of the data. That sounds obvious until you're six months in, you've moved the server to a bind mount for easier backups and forgot the worker, and your schedules run against a directory the UI never reads. Ask me how I know that with a different tool :)

Back up and upgrade NiubiGEO

Everything lives in the niubigeo-data volume. Stop both containers first, because writes are per-file atomic and not transactional, so a tar of a live volume can catch a run half-written:

docker stop niubigeo-worker niubigeo
docker run --rm -v niubigeo-data:/data:ro -v "$PWD":/backup alpine \
  tar -czf /backup/niubigeo-$(date +%F).tar.gz -C /data .
sha256sum niubigeo-$(date +%F).tar.gz
docker start niubigeo niubigeo-worker

Copy the archive off the box. Restoring is the same command in reverse into a fresh volume.

Upgrading: read docs/upgrade.md in the repo first. It's in Chinese, but the gist is that the project makes no promise that a newer version reads an older data directory, and the recommended path is to restore your backup into a seperate directory, start the new version against that copy on another port with no key injected, and only switch over once it opens your projects. For a v0.2.x to v0.2.y bump that's paranoid. For the next big version it's probably right, given how much changed between 0.1 and 0.2.

Install NiubiGEO from source with Node.js 22

If Docker isn't your thing. Ubuntu 24.04 ships Node 18 in apt, which is too old (the package requires 22 or newer), so use NodeSource:

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs git
git clone --branch v0.2.0 --depth 1 https://github.com/Albert-Weasker/niubigeo.git
cd niubigeo
npm ci
cp .env.example .env
nano .env

Set OPENROUTER_API_KEY= in .env, then build and start. npm run server in the README runs the TypeScript through tsx, which is fine for trying it out; for something that stays up I build once and run the compiled file, which is what the container does too:

npm run build
node dist/src/product/product-server.js

npm ci took under three seconds and the build about six on a 4 vCPU box, and node_modules is 74 MB. Now the warning. Started this way the server listens on 0.0.0.0:8787, all interfaces, with no password. I checked. So on a VPS the ufw rules from step 4 aren't optional here, they're the only thing between the internet and your API key. Keep 8787 closed and Caddy in front, and wrap the process in a systemd unit so it survives a reboot:

sudo tee /etc/systemd/system/niubigeo.service > /dev/null <<'EOF'
[Unit]
Description=NiubiGEO workbench
After=network.target

[Service]
WorkingDirectory=/opt/niubigeo
ExecStart=/usr/bin/node dist/src/product/product-server.js
Restart=on-failure
User=niubigeo
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now niubigeo

That assumes you cloned into /opt/niubigeo and created a niubigeo system user who owns the directory. The rest of the hardening for a Node service on a VPS is in securing a Node.js production server and I won't repeat it.

NiubiGEO limitations in v0.2.0

The workbench is not fully in English. The HTML declares lang="zh-CN" and the main navigation is English, then some panels and error messages turn out to be Chinese only. The release notes admit this. The Docker deployment doc and the upgrade doc are Chinese only, full stop, which is half the reason this guide exists. I got through them with a translator open and I'd rather you didn't have to.

The statistics have caveats the maintainers list themselves. A model can claim two "first mentions" in one answer and the aggregate counts that as a conflict, so it's a zero in the stats and it doesn't mean you weren't first. Retries can get mixed with first attempts in some of the numbers. And three runs a few minutes apart prove the repeat workflow works, nothing more. Treat the numbers as a reading and the raw answers as the source of truth, and definately don't put a chart from a week of data in a board deck.

And it's a week-old project with a 0.2.0 tag. I like it a lot, the evidence-first design is exactly what the paid tools don't do, and the same maintainers sell a hosted GEO service so there's a business behind it. But I'd pin the version, back up before every upgrade and expect the next release to move things around. What I don't know yet is how the keyword statistics hold up over a couple of months of scheduled runs, because nobody has had it for a couple of months.

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
Faktureras

VPS.S1

$5.99 Save  17 %
$4.99 Månadsvis
  • 2 vCPU AMD EPYC
  • 2 GB RAMMINNE
  • 30 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår

VPS.S3

$14.99 Save  33 %
$9.99 Månadsvis
  • 4 vCPU AMD EPYC
  • 6 GB RAMMINNE
  • 70 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår

EPYC VPS.P1

$8.99 Save  22 %
$6.99 Månadsvis
  • 2 vCPU AMD EPYC
  • 4 GB RAMMINNE
  • 40 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

EPYC VPS.P2

$16.99 Save  24 %
$12.99 Månadsvis
  • 2 vCPU AMD EPYC
  • 8 GB RAMMINNE
  • 80 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

EPYC VPS.P4

$29.99 Save  23 %
$22.99 Månadsvis
  • 4 vCPU AMD EPYC
  • 16 GB RAMMINNE
  • 160 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

EPYC VPS.P5

$39.99 Save  25 %
$29.99 Månadsvis
  • 8 vCPU AMD EPYC
  • 16 GB RAMMINNE
  • 180 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

EPYC VPS.P6

$59.99 Save  25 %
$44.99 Månadsvis
  • 8 vCPU AMD EPYC
  • 32 GB RAMMINNE
  • 200 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

EPYC VPS.P7

$69.99 Save  29 %
$49.99 Månadsvis
  • 16 vCPU AMD EPYC
  • 32 GB RAMMINNE
  • 240 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

Genoa VPS.G2

$24.99 Save  20 %
$19.99 Månadsvis
  • 2 vCPUAMD EPYC Genoa 4:e generationen 9xx4 med 3,25 GHz eller liknande, på Zen 4-arkitektur. AMD EPYC G4
  • 4 GB DDR5MINNE
  • 50 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

Genoa VPS.G4

$44.99 Save  22 %
$34.99 Månadsvis
  • 4 vCPUAMD EPYC processor med dedikerade vCPU-kärnor, på serverhårdvara för företag. AMD EPYC G4
  • 8 GB DDR5MINNE
  • 100 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

Genoa VPS.G6

$89.99 Save  22 %
$69.99 Månadsvis
  • 8 vCPUAMD EPYC processor med dedikerade vCPU-kärnor, på serverhårdvara för företag. AMD EPYC G4
  • 16 GB DDR5MINNE
  • 200 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

Genoa VPS.G7

$159.99 Save  22 %
$124.99 Månadsvis
  • 8 vCPUAMD EPYC processor med dedikerade vCPU-kärnor, på serverhårdvara för företag. AMD EPYC G4
  • 32 GB DDR5MINNE
  • 250 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna. ingår
  • Gratis auto-backupInnehåller en backupplats som du kan ställa in på daglig, veckovis eller månatlig körning.

AMD Ryzen VPS.R1

$16.99 Save  18 %
$13.99 Månadsvis
  • 1 dedikerad CPU AMD Ryzen 9 7950X med 4,5 GHz eller liknande, på Zen 4-arkitektur. vCPU
  • 4 GB DDR5MINNE
  • 50 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6 ingår IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna.
  • Auto-backup ingår
Beställ nu Upptagen - Tillgänglig

AMD Ryzen VPS.R2

$29.99 Save  17 %
$24.99 Månadsvis
  • 2 dedikerade CPU AMD Ryzen 9 7950X med 4,5 GHz eller liknande, på Zen 4-arkitektur. vCPU
  • 8 GB DDR5MINNE
  • 100 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6 ingår IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna.
  • Auto-backup ingår
Beställ nu Upptagen - Tillgänglig

AMD Ryzen VPS.R4

$109.99 Save  18 %
$89.99 Månadsvis
  • 8 dedikerade CPU AMD Ryzen 9 7950X med 4,5 GHz eller liknande, på Zen 4-arkitektur. vCPU
  • 32 GB DDR5MINNE
  • 400 GB NVMeLAGRING
  • Omätt bandbredd
  • IPv4 & IPv6 ingår IPv6-stöd är för närvarande inte tillgängligt i Frankrike, Finland eller Nederländerna.
  • Auto-backup ingår
Beställ nu Upptagen - Tillgänglig

Questions?

Can I run NiubiGEO with my own OpenAI or Anthropic key instead of OpenRouter?

Not in v0.2.0. The v0.1.0 alpha CLI accepted direct provider keys and the .env.example still lists them, but the current workbench selects models from OpenRouter's catalogue and reads only OPENROUTER_API_KEY (or OPENROUTER_KEY, or the _FILE variants). OpenRouter passes your requests through to the same OpenAI, Anthropic and Google models, so you lose nothing except the ability to bill them directly.