Project N.O.M.A.D. (Network Outage Mitigation And Deployment) is a self-hosted offline knowledge appliance from Crosstalk Solutions. It bundles Wikipedia, encyclopedias, books, offline maps, an AI assistant, and a personal file knowledge base into a single Docker stack that runs on your own hardware and works without an internet connection. After the one-time content download, nothing in your library has to phone home to keep working.
1. Quick Start
Get a working N.O.M.A.D. installation in roughly fifteen minutes on a fresh Debian or Ubuntu machine:
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/install_nomad.sh -o install_nomad.sh
sudo bash install_nomad.sh
When the installer finishes, point a browser on the same LAN at the success URL
it printed (typically http://<host-ip>:8080). On first visit you'll land on
the Easy Setup wizard; follow the prompts to install the AI Assistant,
download a Wikipedia tier, and grab the maps you want.
Tip
The installer accepts everything as defaults. The two values it generates for you —
APP_KEY, MySQL root + user passwords — are written into/opt/project-nomad/compose.yml. Back that file up before you wipe the host.
What you get out of the box
- Full-text Wikipedia (5 size tiers from a 100-article mini up to 100GB+)
- Curated ZIM categories: medical, survival, agriculture, repair, education
- Offline vector maps (Protomaps PMTiles) with custom region downloads
- AI chat (Ollama, with optional RAG over your own files)
- Personal Knowledge Base — drop PDFs, EPUBs, Markdown, and plain text in and the assistant cites them
- Hardware benchmark with community comparison
- Companion services (CyberChef, Flatnotes, Kolibri) installable from Settings
- Web UI on port 8080, log viewer (Dozzle) on port 9999
Prerequisites
- Debian-based Linux (Debian 12+, Ubuntu 22.04+) with
sudo - Docker Engine + Docker Compose v2 plugin (the installer adds them if missing)
- ≥ 8 GB RAM (16 GB+ recommended once you load Wikipedia and run AI)
- ≥ 100 GB free disk for a useful library; SSD strongly preferred
- An NVIDIA GPU is optional but unlocks much faster AI responses
First-run sanity check
docker ps --filter "name=^nomad_" --format "table {{.Names}}\t{{.Status}}"
NAMES STATUS
nomad_admin Up 3 minutes (healthy)
nomad_mysql Up 3 minutes (healthy)
nomad_redis Up 3 minutes (healthy)
nomad_dozzle Up 3 minutes
nomad_updater Up 3 minutes
nomad_disk_collector Up 3 minutes
If any container is missing or unhealthy see the Troubleshooting section.
2. Installation
2.1 System requirements
| Component | Minimum | Recommended |
|---|---|---|
| OS | Debian 12 / Ubuntu 22.04 | Debian 12 / Ubuntu 24.04 |
| CPU | 4 cores | 8+ cores |
| RAM | 8 GB | 32 GB |
| Disk | 100 GB SSD | 1 TB+ NVMe |
| GPU | none (CPU-only AI) | NVIDIA with CUDA 12+ |
| Network | LAN only required | Internet for one-time downloads |
Important
The AI Assistant runs much faster on an NVIDIA GPU. The installer will try to install the NVIDIA Container Toolkit automatically when it sees an NVIDIA card. Failures here are non-fatal — N.O.M.A.D. falls back to CPU inference.
2.2 Install methods
Automated (recommended)
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/install_nomad.sh -o install_nomad.sh
sudo bash install_nomad.sh
The script installs Docker if missing, sets up the NVIDIA runtime when a
GPU is detected, generates a random APP_KEY plus MySQL passwords, drops
a fully-wired compose.yml into /opt/project-nomad, and brings the stack
up with docker compose up -d.
Manual compose
sudo mkdir -p /opt/project-nomad
cd /opt/project-nomad
sudo curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/management_compose.yaml -o compose.yml
# Edit compose.yml and replace every "replaceme" placeholder
sudo docker compose -p project-nomad up -d
Use this when you need to customize ports, volume paths, or environment values before the first run.
2.3 Helper scripts the installer drops in /opt/project-nomad/
| Script | What it does |
|---|---|
start_nomad.sh |
Starts every container whose name begins with nomad_ |
stop_nomad.sh |
Gracefully stops the same set |
update_nomad.sh |
Pulls latest images and force-recreates containers |
uninstall_nomad.sh |
Tears down the stack; optionally deletes /opt/project-nomad |
Warning
uninstall_nomad.shwill offer to delete/opt/project-nomad. Accepting permanently removes your MySQL data, Redis cache, downloaded ZIMs, maps, uploaded knowledge-base files, and benchmark history. There is no undo.
2.4 Verifying the install
Open http://<host-ip>:8080 in any browser on the LAN. You should see the
N.O.M.A.D. dashboard with the powered-by-Crosstalk badge in the footer. The
URL bar will redirect from / to /home.
To verify the API is up:
curl http://localhost:8080/api/health
{"status":"ok"}
2.5 Post-install: Easy Setup wizard
The first time you load /easy-setup you'll be asked to:
- Install the AI Assistant (Ollama) container, or point N.O.M.A.D. at an existing remote Ollama / LM Studio / llama.cpp server you already run.
- Pick a Wikipedia size tier (or skip and add it later).
- Choose curated content tiers by category (medical, survival, repair, etc.) — each tier adds a coherent bundle of ZIM files.
- Download base map assets (Protomaps fonts, sprites, layer styles).
Everything chosen here runs as background download jobs you can monitor from the dashboard.
3. Configuration
Most users never need to touch compose.yml after install — every day-to-day
setting is in the web UI under Settings → System and the per-feature
settings pages. The full environment-variable reference lives in the Admin
Guide; this section covers what an OSS user typically wants.
3.1 Where things live
| Path | Purpose |
|---|---|
/opt/project-nomad/compose.yml |
Container definitions, env vars, ports |
/opt/project-nomad/storage/ |
ZIMs, maps, uploads, logs, disk-info JSON |
/opt/project-nomad/mysql/ |
MySQL data (users + sessions + settings) |
/opt/project-nomad/redis/ |
Redis queue persistence |
3.2 Common UI settings
| Setting | Where | Notes |
|---|---|---|
| Theme (light/dark) | Top-right toggle | Persisted per-browser |
| AI Assistant custom name | Settings → Models | Renders everywhere "AI Assistant" appears |
| Chat suggestions | Settings → Models | Toggles the prompt-starter chips on the chat home |
| Remote Ollama URL | Settings → Models | Point at an existing LLM server instead of running one locally |
| Anonymous benchmark sharing | Settings → Benchmark | Strips builder-tag before submitting |
| Early-access flag | Settings → System | Enables in-development features |
Tip
If you renamed the AI Assistant in Settings → Models, the new name propagates everywhere — page titles, system prompts, chat UI — within about sixty seconds (the name is cached for one minute server-side).
3.3 Changing the listening port
Edit the admin port mapping in compose.yml:
services:
admin:
ports:
- "9090:8080" # access at http://host:9090, container still uses 8080 internally
Then restart:
sudo /opt/project-nomad/update_nomad.sh
Note
Don't change
PORT=8080inside theenvironment:block — it's the in-container port and the health check is wired to it. Map it differently on the host instead.
4. Core Usage
4.1 Reading offline content
The Content Explorer (/home) lists every installed Wikipedia language pack,
ZIM bundle, and curated tier. Click any tile to open it in the embedded Kiwix
reader, which serves articles directly from the ZIM file under
/opt/project-nomad/storage/zim/.
Search inside a ZIM uses the Kiwix full-text index. Cross-ZIM search isn't exposed in the UI; use the AI Assistant with Knowledge Base mode for that.
4.2 Chatting with the AI Assistant
Visit /chat (or click the chat bubble in the corner of any page).
1. Pick or download a model from the model dropdown at the top
2. Optionally turn on Knowledge Base for RAG over your uploaded files
3. Type a question — responses stream in real time
The assistant:
- Streams tokens as they arrive (cancellable mid-stream)
- Remembers conversation history for the active session
- Auto-generates a session title from the first turn
- Supports "thinking" models (deepseek-r1, qwen3, gpt-oss, etc.) and renders the thought stream in a collapsible panel
- Honors model-size-aware RAG context limits so small models aren't drowned
| Action | How |
|---|---|
| New chat | Click + New Chat in the sidebar |
| Switch model | Top-right dropdown; choice persists per-browser |
| Stop generating | Press the stop button while streaming |
| Delete one session | Hover a session in the sidebar → trash icon |
| Clear all history | Sidebar → trash-all icon → confirm modal |
| Knowledge Base toggle | Switch in the chat header |
Note
The first response with a freshly-downloaded model is slower than subsequent ones — Ollama warms its cache on the first generate call.
4.3 Building your knowledge base (RAG)
The Knowledge Base modal (chat → folder icon) is your file uploader. Drop in
PDFs, EPUBs, Markdown, plain text, or HTML and they get chunked, embedded with
nomic-embed-text (downloaded automatically), and stored in Qdrant.
Once a file finishes indexing it shows up in the Files list. Asking a chat question with Knowledge Base on:
- Rewrites your query using the conversation history (when you've had ≥ 3 user turns) so the search is context-aware.
- Searches Qdrant for the top-5 most similar chunks above a similarity score
of
0.3. - Trims results to fit the active model's context window (1B–3B models get 2 results / 1k tokens, 4–8B get 4 results / 2.5k tokens, 13B+ get 5 with no cap).
- Injects them as a system message and asks the model to cite them.
Supported formats and how they're handled:
| Type | Parser |
|---|---|
.pdf |
pdf-parse text extraction; OCR via tesseract.js falls back if PDF is image-only |
.epub |
Parses META-INF/container.xml → opf → spine, strips boilerplate |
.md / .markdown |
Markdoc-aware so headings become chunk boundaries |
.txt |
Direct chunking |
.html / .htm |
DOM-aware extraction with cheerio, strips nav/footer/scripts |
Tip
Configure a remote Ollama (Settings → Models → Remote Ollama URL) to keep your library on a small box and run the AI on a beefier GPU machine. Just remember to start that remote with
OLLAMA_HOST=0.0.0.0so it accepts LAN connections.
4.4 Maps
The Maps page renders a maplibre-gl base map from offline Protomaps tiles. Functions:
- Pan/zoom the global low-zoom basemap.
- Download high-detail PMTiles regions from the curated map collections list,
or paste any
.pmtilesURL into Download Custom. - Drop pins at any latitude/longitude with name + color (route support is scaffolded but UI-final in a future release).
- Listed regions live under
/opt/project-nomad/storage/maps/and are served by the admin container on/storage/maps/...routes.
4.5 Installing companion apps
Settings → Apps lists the available services that ship as separate Docker containers. Click Install on any tile and N.O.M.A.D. pulls the image, wires it to the project network, and exposes its port through the admin UI's service catalog:
| Service | Default port | Purpose |
|---|---|---|
nomad_kiwix_server |
8086 | ZIM reader (always installed alongside any ZIM tier) |
nomad_ollama |
11434 | Local LLM runtime |
nomad_qdrant |
6333 | Vector DB powering the Knowledge Base |
nomad_cyberchef |
8000 | The Cyber Swiss Army Knife |
nomad_flatnotes |
8200 | Self-hosted markdown notes |
nomad_kolibri |
8080 (mapped) | Open educational resource library |
Warning
Service install pulls images from public registries (GHCR, Docker Hub). Make sure your host has internet access — the install will fail with "Pre-install action failed" otherwise.
4.6 Benchmarking
/settings/benchmark runs a calibrated 0–100 score combining sysbench (CPU,
RAM, disk read, disk write) with an Ollama inference test on llama3.2:1b.
Full benchmark System + AI (recommended)
System benchmark CPU/RAM/disk only — works without AI Assistant
AI benchmark Inference timing only — requires AI Assistant
The composite score uses these weights:
| Component | Weight |
|---|---|
| AI tokens/sec | 30 % |
| CPU events/sec | 25 % |
| Memory ops/sec | 15 % |
| AI time-to-first-token | 10 % |
| Disk read MB/s | 10 % |
| Disk write MB/s | 10 % |
Submitting a Full benchmark to benchmark.projectnomad.us adds your hardware
to the public comparison chart. Toggle anonymous in the submit modal to
strip your builder-tag.
5. Common Tasks
5.1 Add Wikipedia after the wizard
- Go to Settings → ZIM.
- Click Wikipedia tab.
- Pick a size tier (Mini → Full); the size in MB is shown.
- Click Install — a download job starts.
- Watch progress on the dashboard's Active Downloads card.
5.2 Switch the AI Assistant to a remote server
- Stand up Ollama or LM Studio elsewhere with
OLLAMA_HOST=0.0.0.0. - Settings → Models → Remote Ollama URL → paste
http://<remote>:11434. - Click Test & Save. N.O.M.A.D. will probe
/v1/models; on success it marks the local Ollama service as uninstalled and routes all chat traffic to the remote. - To revert, clear the field and Save.
Important
Remote mode disables chat suggestions and does not auto-pull models for you — the remote box owns its own model library.
5.3 Re-index a single file in the knowledge base
- Chat → Knowledge Base → Files.
- Click the trash icon next to the file → confirm.
- Drag the file in again. The embedder rejects duplicates by SHA-256 hash, so the new copy must be different from the old one in some way (open it in an editor, save, retry) if you're testing.
5.4 Move N.O.M.A.D. to a bigger disk
sudo /opt/project-nomad/stop_nomad.shsudo rsync -aHAXxv /opt/project-nomad/ /mnt/newdisk/project-nomad/- Edit
compose.ymlso every/opt/project-nomad/...volume points at/mnt/newdisk/project-nomad/.... sudo docker compose -p project-nomad -f /mnt/newdisk/project-nomad/compose.yml up -d
5.5 Update to the latest version
In the UI: Settings → Update → Check for updates → Update Now. The
sidecar updater rewrites the admin image tag in compose.yml, pulls fresh
images, and recreates admin, mysql, redis, and dozzle one at a time.
You can watch the progress bar live.
From the shell:
sudo /opt/project-nomad/update_nomad.sh
5.6 Open the container log viewer
Click Service Logs & Metrics under Settings → System (or browse to
http://<host>:9999). Dozzle ships with N.O.M.A.D. for visual log access; it
runs read-only — start/stop/exec actions are intentionally disabled.
5.7 Drop pins on the map
- Maps page → click anywhere on the map.
- The marker panel slides in from the right.
- Enter a name, pick a color, Save.
- Markers persist in MySQL and reappear on every visit.
5.8 Migrate from old disk-collector script
Older installs ran collect_disk_info.sh as a nohup background process on
the host that wrote to /tmp/nomad-disk-info.json. The new architecture uses
a sidecar container. To migrate:
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/migrate-disk-collector.sh -o migrate.sh
sudo bash migrate.sh
The script stops the old PID, removes the old /tmp/nomad-disk-info.json
bind-mount from compose.yml, adds the disk-collector service, and brings
the stack back up.
6. Troubleshooting
Error: Container nomad_mysql is unhealthy
Cause: Stale MySQL data directory from a previous install attempt — the
MYSQL_*_PASSWORD values in compose.yml no longer match what's in the
on-disk databases. (See issue #630.)
Solution:
sudo /opt/project-nomad/stop_nomad.shsudo rm -rf /opt/project-nomad/mysqlsudo /opt/project-nomad/start_nomad.sh
This forces MySQL to reinitialize with the credentials currently in
compose.yml.
Prevention: Use the official installer; if you customize, never delete
just compose.yml without also wiping the MySQL data directory.
Error: Failed to load the XML library file '/data/kiwix-library.xml'
Cause: The Kiwix container started with an empty library (no ZIM files yet) — Kiwix can refuse to boot until it has at least one ZIM. Tracked as issue #710.
Solution: Install at least one ZIM tier from Settings → ZIM and the
admin container will rebuild kiwix-library.xml and restart Kiwix
automatically. The KiwixMigrationProvider handles legacy configs on every
admin startup as well.
Error: Ollama is not running or not accessible (ECONNREFUSED)
Cause: Either the AI Assistant container isn't installed, has crashed, or your remote Ollama URL is unreachable.
Solution:
- Settings → Apps — confirm "AI Assistant" shows as installed and running.
- If running, check
docker logs nomad_ollamafor crash details. - For remote mode, confirm the remote is up:
curl http://<remote>:11434/v1/models. If that fails, the issue is network or the remote isn't bound to0.0.0.0.
Prevention: Pin a stable Ollama image tag in compose.yml and don't
prune Docker volumes named nomad_ollama_* blindly.
Error: GPU is not detected by Nomad
Cause: NVIDIA Container Toolkit not installed or Docker daemon not
configured for the nvidia runtime. Open as issue
#727.
Solution:
nvidia-smi # Confirms host sees the GPU
docker info | grep -i nvidia # Confirms Docker can use it
If the second command finds nothing:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
sudo /opt/project-nomad/update_nomad.sh # Recreate containers with the runtime
Prevention: Run the official installer on a host where nvidia-smi
already works; the installer handles the rest.
Error: Flatnotes not working both locally and remotely. ERR_CONNECTION_REFUSED on port 8200
Cause: Flatnotes container failed to start, or its port mapping conflicts with another service. See issue #709.
Solution:
docker logs nomad_flatnotes
docker compose -p project-nomad restart flatnotes
If it loops, Settings → Apps → Flatnotes → Force Reinstall rebuilds the container from scratch with default config.
Error: nomad_qdrant may fail and never come back online
Cause: Qdrant data corruption or insufficient disk during a write, documented in issue #673.
Solution:
docker logs nomad_qdrant— look for "WAL", "snapshot", or panic lines.- If the WAL is corrupt, the safe fix is to delete the qdrant data directory
and re-embed your files:
docker compose -p project-nomad stop qdrant && sudo rm -rf /opt/project-nomad/qdrant && docker compose -p project-nomad up -d qdrant.
Prevention: Watch the Storage gauge on the dashboard — Qdrant gets unhappy when free disk drops below ~1 GB.
Error: Storage bar graph does not correctly reflect size and space available on custom partition
Cause: The disk-collector sidecar reads from the host's mount table via
/host/proc/1/mounts. If you moved /opt/project-nomad to a non-default
mount that isn't visible in the host's namespace, the gauge can be wrong.
Tracked as issue #716.
Solution: Bind the actual storage partition into the disk-collector at its real host mount path or run the migration script from §5.8 again after moving directories.
Error: Installing a AI model, removing, and attempting to reinstall fails
Cause: Ollama keeps a tombstone for recently-deleted models for a short window. Issue #690.
Solution: Wait ~60 seconds and retry the download, or docker exec nomad_ollama ollama list to verify the model is fully gone before reinstalling.
Diagnostic decision tree
Web UI won't load?
├── docker ps shows nomad_admin?
│ ├── No → docker compose up -d in /opt/project-nomad
│ └── Yes → curl http://localhost:8080/api/health
│ ├── Returns {"status":"ok"} → check firewall / reverse proxy
│ └── Errors → docker logs nomad_admin
└── Containers all up but UI 502s?
└── nomad_admin healthy? (docker ps STATUS column)
├── No → check MySQL/Redis dependencies, see "unhealthy" entries above
└── Yes → clear browser cache / try incognito
Warning
Do not run
docker system prune -a --volumeson the N.O.M.A.D. host. It will delete the named volumes that hold MySQL data, Redis state, and the shared update channel between admin and the updater sidecar.
Caution
The MySQL
MYSQL_ROOT_PASSWORDand the adminAPP_KEYare written intocompose.ymlin plain text. Anyone with shell access to the host can read them. Don't expose/opt/project-nomad/compose.ymlover a public file share.
7. FAQ
Q: Does N.O.M.A.D. need an internet connection to run?
Only for the one-time content downloads, container image pulls, and update checks. Once everything is local, the entire stack works on a fully air-gapped LAN. The dashboard's "Internet Status" indicator just tells you whether outgoing connectivity is available — it never gates functionality.
Q: Can I run it without a GPU?
Yes. AI inference falls back to CPU. Expect single-digit tokens/sec on small models (1B–3B parameters) and very slow responses on anything larger. The benchmark gives you a concrete number for your hardware.
Q: Where are my uploaded knowledge-base files actually stored?
/opt/project-nomad/storage/kb_uploads/. Embeddings live in Qdrant under
/opt/project-nomad/qdrant/. Deleting one without the other leaves the
system in a confused state — use the UI Delete button instead, which
clears both.
Q: How do I back up my N.O.M.A.D. data?
Stop the stack first to ensure consistent MySQL and Qdrant state, then
archive /opt/project-nomad:
sudo /opt/project-nomad/stop_nomad.sh
sudo tar czf nomad-backup-$(date +%F).tar.gz -C / opt/project-nomad
sudo /opt/project-nomad/start_nomad.sh
ZIM files and PMTiles are big — exclude storage/zim and storage/maps if
you don't need them in the backup; they can be re-downloaded.
Q: Can I run two N.O.M.A.D. instances on the same host?
Not without significant rework — the install script hardcodes the project
name project-nomad and the install path /opt/project-nomad. Different
ports alone won't avoid container-name collisions.
Q: How do I disable the AI Assistant entirely?
Settings → Apps → AI Assistant → Uninstall. Or, in compose.yml, never
install the Ollama service. The chat icon in the UI will redirect to a
"Service not installed" page.
Q: Which models are recommended?
The fallback recommendations are llama3.1, deepseek-r1, and llama3.2.
The full Available Models picker pulls a live, sortable catalog from
Ollama's registry. For the Knowledge Base, ensure qwen2.5:3b is installed —
it's the default query-rewriter and the chat will just skip rewriting if
it's missing.
Q: How do I unblock submitting a benchmark?
Run a Full benchmark (not System-only or AI-only) on a host where the AI Assistant is installed and reachable. Only Full benchmarks with non-zero AI data are accepted by the public repository.
Q: Does N.O.M.A.D. send telemetry?
No analytics, ads, or telemetry. The only outbound calls are explicit
user-initiated actions: downloading content from your chosen sources,
checking GitHub for new releases, optionally submitting your benchmark to
benchmark.projectnomad.us, and pulling images during updates.
Q: How do I file a bug?
github.com/Crosstalk-Solutions/project-nomad/issues. The Settings → Support → Generate Debug Info button bundles a sanitized state dump you can attach.