If you've tried to run Project N.O.M.A.D. on a TerraMaster NAS and hit a wall of "Permission denied" errors and containers that won't stay up — this post is for you. After real-world testing on a TerraMaster F4-423, I've got a working config that handles every quirk TOS 7 throws at you.

But before we get into the YAML, let's talk about what we're building here and why it matters.


What Is Project N.O.M.A.D.?

Project N.O.M.A.D. (Networked Offline Mobile Autonomous Deployment) is an open-source, self-hosted platform that bundles a curated stack of offline knowledge, AI, and educational tools into a single Docker Compose setup. It's maintained by CrossTalk Solutions and designed to run on hardware you own — no cloud required.

The stack includes:

  • Kiwix — offline Wikipedia, medical references, Stack Overflow snapshots, and hundreds of other ZIM-format knowledge bases
  • Ollama — local LLM inference, so you have AI assistance without a connection
  • Kolibri — offline educational content from Khan Academy and others
  • Flatnotes — a clean, markdown-based personal wiki and note system
  • CyberChef — the GCHQ data transformation tool, fully offline
  • Qdrant — local vector database for AI-powered search across your knowledge base

The whole thing is managed through a clean web admin panel. You add content packs, enable or disable services, and N.O.M.A.D. handles the Docker orchestration. The GitHub repository has full documentation and an active issue tracker.


The TerraMaster F4-423: A Purpose-Built Offline Knowledge Machine

The TerraMaster F4-423 is a 4-bay NAS running an Intel Celeron N5105 quad-core processor with up to 32GB of DDR4 RAM. It runs TerraMaster OS (TOS), which is a Linux-based NAS operating system with a Docker app built in.

For a NOMAD deployment, the specs line up well:

  • N5105 CPU — efficient enough for 24/7 operation, capable enough to run smaller Ollama models (Phi-3 mini, Llama 3.2 3B) without a GPU
  • 32GB RAM ceiling — Ollama appreciates headroom; 16GB is the comfortable floor for running a model alongside the rest of the stack
  • 4-bay storage — room for RAID redundancy on your knowledge base and still have capacity for ZIM files, which can run large (a full English Wikipedia ZIM is ~100GB)
  • Low idle power draw — reported at ~15–20W at idle, which matters when you're running this around the clock or on backup power

For a "just works" offline server, it's a solid choice. The F4-424 Pro steps up to an N305 CPU and 64GB RAM if you want to run larger models — but for most N.O.M.A.D. use cases, the F4-423 is the sweet spot.


Why a NAS Is the Right Hardware for This

Let me be direct about what Project N.O.M.A.D. is actually for: it's the knowledge infrastructure you want running when normal infrastructure stops being reliable.

Power outages, ISP failures, natural disasters, network-level disruptions — in any scenario where internet access goes away, a NAS running N.O.M.A.D. becomes your local library, your AI assistant, your medical reference, your educational resource, and your secure note system. All of it accessible from any device on your local network with nothing but a browser.

TerraMaster NAS hardware is particularly well-suited to this because:

  • Designed for always-on operation — the hardware, thermal design, and TOS software are all built around 24/7 uptime, not desktop duty cycles
  • Scheduled power on/off — TOS supports automatic boot on a schedule, so you can have it power on, pull container updates, and be ready before you need it — without leaving it running 24/7 if that's not your preference
  • UPS support — TOS integrates with USB UPS devices for graceful shutdown during power events, protecting your data
  • RAID storage — your offline knowledge base is too valuable to lose to a single drive failure; the 4-bay design lets you run RAID 5 with a hot spare
  • Low power — 15-20W idle means it can run on a modest battery backup for hours, not minutes

When the excrement hits the rotating wind device, the last thing you want is to be troubleshooting a laptop that runs hot, a Raspberry Pi that's too slow to serve Ollama, or a cloud service that's unreachable. A purpose-built NAS with a solid RAID array and a UPS behind it is infrastructure you can actually rely on.

Note: I'll cover the full hardware build — drives, UPS, network config, and how I have the physical setup organized — in a follow-up post in the Builds section.


The TOS 7 Problem (And Why the Normal Install Doesn't Work)

Before the working config, the issues worth understanding:

  • Docker runs as the first user, not root. On TOS, containerd and the Docker daemon run as the admin user you created during setup — not as true root. Any container that expects to write as root will fail silently or throw permission errors.
  • TerraMaster's Advanced ACL system is aggressive. TOS applies ACLs to volumes that override standard Unix permissions. Folders created through the TOS UI may be inaccessible to Docker containers even with 777 permissions set.
  • The official N.O.M.A.D. updater can break on TOS. It sometimes fails to apply new features cleanly because of how TOS handles Docker socket access. Manual docker-compose.yml management is more reliable.

The fixes for all three are in the config below.


Lessons Learned (Read Before You Deploy)

  • Force services that hit permission issues to run as user: "0:0" — this applies especially to Kiwix, Ollama, and Kolibri
  • Use the real volume path in all mounts: /Volume1/docker/nomad/..., not relative paths or symlinks
  • Set ownership of your storage/ folder tree to 0:0 (root) with 755 permissions before first launch:
    chown -R 0:0 /Volume1/docker/nomad/storage
    chmod -R 755 /Volume1/docker/nomad/storage
- After any significant config change: `docker-compose down && docker-compose up -d --remove-orphans` - The `mysql` and `redis` health checks in the admin service's `depends_on` are important — without them the admin container starts before the database is ready --- ## Recommended Folder Structure Create this on your NAS before deploying:
/Volume1/docker/nomad/
├── docker-compose.yml
├── mysql/
├── redis/
└── storage/
    ├── zim/
    ├── flatnotes/
    ├── ollama/
    ├── kolibri/
    └── qdrant/

--- ## The Working docker-compose.yml This is the config that runs cleanly on TOS 7 after everything else failed. Replace `YOUR-NAS-IP` with your NAS's local IP, and change all placeholder passwords before deploying.
name: project-nomad

services:
  admin:
    image: ghcr.io/crosstalk-solutions/project-nomad:latest
    pull_policy: always
    container_name: nomad_admin
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"
    ports:
      - "8080:8080"
    volumes:
      - /Volume1/docker/nomad/storage:/app/storage
      - /var/run/docker.sock:/var/run/docker.sock
      - nomad-update-shared:/app/update-shared
    environment:
      - NODE_ENV=production
      - PORT=8080
      - LOG_LEVEL=info
      - APP_KEY=ChangeThisToAStrongRandom32CharString123
      - HOST=0.0.0.0
      - URL=http://YOUR-NAS-IP:8080
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_DATABASE=nomad
      - DB_USER=nomad_user
      - DB_PASSWORD=ChangeThisToAStrongPassword123!
      - DB_NAME=nomad
      - DB_SSL=false
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  dozzle:
    image: amir20/dozzle:v10.0
    container_name: nomad_dozzle
    restart: unless-stopped
    ports:
      - "9999:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DOZZLE_ENABLE_ACTIONS=false
      - DOZZLE_ENABLE_SHELL=false

  mysql:
    image: mysql:8.0
    container_name: nomad_mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=ChangeThisToAStrongPassword123!
      - MYSQL_DATABASE=nomad
      - MYSQL_USER=nomad_user
      - MYSQL_PASSWORD=ChangeThisToAStrongPassword123!
    volumes:
      - /Volume1/docker/nomad/mysql:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: nomad_redis
    restart: unless-stopped
    volumes:
      - /Volume1/docker/nomad/redis:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

  updater:
    image: ghcr.io/crosstalk-solutions/project-nomad-sidecar-updater:latest
    pull_policy: always
    container_name: nomad_updater
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /Volume1/docker/nomad:/opt/project-nomad
      - nomad-update-shared:/shared

  disk-collector:
    image: ghcr.io/crosstalk-solutions/project-nomad-disk-collector:latest
    pull_policy: always
    container_name: nomad_disk_collector
    restart: unless-stopped
    volumes:
      - /:/host:ro,rslave
      - /Volume1/docker/nomad/storage:/storage

  # ── Services ─────────────────────────────────────────────────

  kiwix:
    image: ghcr.io/kiwix/kiwix-serve:latest
    container_name: nomad_kiwix_server
    user: "0:0"
    restart: unless-stopped
    ports:
      - "8090:8080"
    volumes:
      - /Volume1/docker/nomad/storage/zim:/data
    command: --library /data/kiwix-library.xml --monitorLibrary

  flatnotes:
    image: dullage/flatnotes:v5.5.4
    container_name: nomad_flatnotes
    restart: unless-stopped
    ports:
      - "8200:8080"
    volumes:
      - /Volume1/docker/nomad/storage/flatnotes:/app/data
    environment:
      - FLATNOTES_AUTH_TYPE=password
      - FLATNOTES_USERNAME=admin
      - FLATNOTES_PASSWORD=ChangeThisToAStrongPassword123!
      - FLATNOTES_SECRET_KEY=ChangeThisToARandomLongString123456789

  ollama:
    image: ollama/ollama:0.20.5
    container_name: nomad_ollama
    user: "0:0"
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - /Volume1/docker/nomad/storage/ollama:/root/.ollama
    environment:
      - OLLAMA_NUM_PARALLEL=4
      - OLLAMA_MAX_LOADED_MODELS=2

  kolibri:
    image: treehouses/kolibri:0.12.8
    container_name: nomad_kolibri
    user: "0:0"
    restart: unless-stopped
    ports:
      - "8300:8080"
    volumes:
      - /Volume1/docker/nomad/storage/kolibri:/root/.kolibri

  cyberchef:
    image: ghcr.io/gchq/cyberchef:10.23
    container_name: nomad_cyberchef
    restart: unless-stopped
    ports:
      - "8100:80"

  qdrant:
    image: qdrant/qdrant:v1.16
    container_name: nomad_qdrant
    restart: unless-stopped
    ports:
      - "6333-6334:6333-6334"
    volumes:
      - /Volume1/docker/nomad/storage/qdrant:/qdrant/storage

volumes:
  nomad-update-shared:
    driver: local

--- ## Auto-Start Script for TOS Scheduled Power-On If you use TOS's scheduled power on/off (Power Management → Schedule), this startup script handles the delay needed for Docker and networking to fully initialize after boot:
#!/bin/bash
# /Volume1/docker/nomad/nomad-startup.sh
# Project N.O.M.A.D. startup for TerraMaster TOS 7 — optimized for scheduled power-on

echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting Project N.O.M.A.D. after boot..." >> /var/log/nomad-startup.log

# Allow Docker and network stack to fully initialize
sleep 35

cd /Volume1/docker/nomad

echo "$(date '+%Y-%m-%d %H:%M:%S') - Pulling latest images..." >> /var/log/nomad-startup.log
docker-compose pull --quiet

echo "$(date '+%Y-%m-%d %H:%M:%S') - Starting containers..." >> /var/log/nomad-startup.log
docker-compose up -d --remove-orphans

echo "$(date '+%Y-%m-%d %H:%M:%S') - Startup complete." >> /var/log/nomad-startup.log

Add this to TOS's Task Scheduler (Control Panel → Task Scheduler) set to run on system startup. The 35-second sleep is not optional — the N5105 needs time after a scheduled power-on before `containerd` is fully ready.

Service Access URLs

Once everything is up, your stack is available at:













































Service URL Notes
N.O.M.A.D. Admin http://YOUR-NAS-IP:8080 Main dashboard — manage content packs here
Kiwix http://YOUR-NAS-IP:8090 Offline Wikipedia, medical refs, Stack Overflow
Flatnotes http://YOUR-NAS-IP:8200 Personal wiki and markdown notes
CyberChef http://YOUR-NAS-IP:8100 Data transformation toolkit
Kolibri http://YOUR-NAS-IP:8300 Offline educational content
Ollama http://YOUR-NAS-IP:11434 Local LLM inference endpoint
Dozzle (logs) http://YOUR-NAS-IP:9999 Container log viewer


What's Next

This covers the Docker config and the TOS-specific gotchas. The follow-up Builds post will cover the full physical setup: drive selection and RAID config, UPS integration, network segmentation, and how this fits into a broader preparedness infrastructure.

If you're running into issues with a specific service or a different TerraMaster model, the Project N.O.M.A.D. GitHub issues are active and worth checking before debugging blind.