Getting an edge AI demo to run on your desk is the easy part. Keeping it alive on a $100 gateway in a factory cabinet, surviving power cuts, OTA updates, and the occasional kill -9 from a well-meaning technician — that's the job.
NeoMind is an open-source, Rust-powered edge AI platform for IoT automation: a single binary that bundles an HTTP API (Axum), an embedded MQTT broker, a rule engine, an AI agent runtime, and embedded storage (redb). One process, two ports, no external database. That design choice pays off exactly at the deployment stage.
This walkthrough covers the two production deployment paths available in the current release (v0.9.18, commit c0306de44d11), plus the operational details that separate a demo from a deployment: restarts, hardening, health checks, updates, and local LLM integration.
Two paths, one binary
NeoMind ships pre-built release binaries for linux/amd64, linux/arm64, and darwin/arm64, so you have two sane options:
- Bare metal with systemd — smallest footprint, best for constrained gateways (2-4 GB RAM devices).
-
Docker / Docker Compose — multi-arch images on Docker Hub (
camthink/neomind:latest), best when you already run containers or want the bundled local LLM stack.
Both paths expose the same surface: port 9375 for the HTTP API + Web UI + WebSocket, and port 1883 for the embedded MQTT broker that your devices connect to.
Path 1: one-line install + systemd
The repo ships an install script (scripts/install.sh) that detects your OS and architecture, resolves the latest release, and drops everything in place:
curl -fsSL https://raw.githubusercontent.com/camthink-ai/NeoMind/main/scripts/install.sh | sudo bash
What it does under the hood (and the knobs you can set as environment variables):
-
INSTALL_DIR=/usr/local/bin— theneomindbinary -
DATA_DIR=/var/lib/neomind— all persistent state (redb database, rules, config) -
WEB_DIR=/var/www/neomind— the built-in web UI static files -
PORT=9375,NO_WEB=1,NO_SERVICE=1,USE_NGINX=1for non-standard setups
The script also installs a systemd unit (scripts/neomind.service) that is worth reading because it encodes a solid production baseline:
[Service]
Type=simple
User=neomind
WorkingDirectory=/var/lib/neomind
ExecStart=/usr/local/bin/neomind
Restart=always
RestartSec=5
Environment="RUST_LOG=info"
Environment="NEOMIND_DATA_DIR=/var/lib/neomind"
Environment="NEOMIND_BIND_ADDR=0.0.0.0:9375"
NoNewPrivileges=true
ProtectSystem=strict
MemoryMax=2G
Three details here are doing real work:
-
Restart=always+RestartSec=5handles the reality of edge power and networking. The box will brown-out; the service comes back without anyone driving to the site. -
ProtectSystem=strictplus a dedicatedneominduser means the process can only write to explicitly allowed paths. If anything ever escapes into the process, the blast radius is tiny. -
MemoryMax=2Gis a cgroup hard cap. On a shared gateway, NeoMind cannot eat the memory that your other services need.
Check it with the usual tools:
sudo systemctl status neomind
sudo journalctl -u neomind -f
Path 2: Docker Compose (with optional local LLM)
If your fleet already runs containers, the compose path is two commands:
git clone https://github.com/camthink-ai/NeoMind.git
cd NeoMind && docker compose up -d
The docker-compose.yml pulls camthink/neomind:latest — a multi-arch image (amd64 + arm64), so the same compose file works on an x86 gateway and a Raspberry Pi 5 or Jetson-class board:
services:
neomind:
image: camthink/neomind:latest
restart: unless-stopped
ports:
- "9375:9375" # HTTP API + Web UI + WebSocket
- "1883:1883" # MQTT broker
volumes:
- neomind-data:/app/data
Two implementation details from the project's Dockerfile that I think are worth knowing, because they answer questions you'd otherwise hit the hard way:
Why Ubuntu 22.04 and not Alpine? NeoMind extensions ship native binaries built against glibc. A musl-based Alpine image cannot dlopen a glibc-linked shared library, so the extension marketplace would be unusable. The runtime stage pins glibc 2.35 — identical to the bare-metal release baseline — so Docker and bare metal load the exact same extension binaries.
Why does arm64 need a jemalloc flag? The build sets JEMALLOC_SYS_WITH_LG_PAGE=16 for ARM targets because hosts like Raspberry Pi 5 and Jetson can run 64 KB memory pages; without the flag the allocator crashes. If you build your own ARM container images for edge workloads, this is a classic trap.
The image also ships a built-in health check:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:9375/api/health || exit 1
So docker ps and any container orchestrator can tell you whether the node is actually serving, not merely running.
Local AI without extra plumbing
Here's where it gets interesting for the AI-at-the-edge crowd. The repo includes a docker-compose.override.yml that is auto-loaded by docker compose and adds a complete local LLM stack:
- llama-init: a one-shot container that downloads a Gemma 4 E2B GGUF (q4_0, ~4.3 GB, resumable) into a named volume
- llama: a llama.cpp server with a 16K context window on port 8080
- neomind: waits for the model server to become healthy, then auto-registers it as an AI backend
That's right: docker compose up -d gives you an IoT platform with a fully local, no-API-key LLM attached. On constrained hardware you'll want a small model — Gemma's E2B variant is built exactly for edge devices, where 7B+ models typically crawl at 1-2 tokens/second on CPU-only boards. First boot downloads the model; every restart after that reuses the volume.
To disable local AI, delete or rename the override file — the core platform runs fine without it.
The operations checklist
A few things that make day-2 operations painless:
Reverse proxy / TLS. Put nginx or Caddy in front of port 9375 for TLS. The install script has USE_NGINX=1 for this. Keep port 1883 (MQTT) firewalled to your device VLAN — device traffic should never traverse your public ingress.
Updates. Bare metal: re-run the install script (it resolves the latest release automatically) and sudo systemctl restart neomind. Docker: docker compose pull && docker compose up -d — the compose file sets pull_policy: always. Because all state lives in one place (/var/lib/neomind bare metal, neomind-data volume in Docker), updates are just "replace binary/image, keep data directory". Back up that one directory and you have everything.
Monitoring. The /api/health endpoint is your liveness probe; journalctl -u neomind (or docker compose logs -f neomind) is your log stream. MemoryMax=2G in the systemd unit keeps resource surprises out of your dashboards.
Secrets. In Docker, set NEOMIND_JWT_SECRET for stable auth across restarts and NEOMIND_ENCRYPTION_KEY for persistent data encryption — both documented in the compose file. On bare metal, drop them into an override env file rather than editing the unit.
Single binary, small ops team
The recurring theme in NeoMind's deployment story is fewer moving parts. No external broker, no external database, no separate web server. One process with a hard memory cap, one data directory to back up, two ports to firewall. When your "site" is a cabinet in a building you visit twice a year, boring is a feature.
If you run edge gateways — or you're evaluating what an on-prem AI + IoT stack looks like without cloud lock-in — give it a spin and tell the maintainers what breaks: github.com/camthink-ai/NeoMind.
Deployment details in this article reflect the NeoMind repository at commit c0306de44d11 (release v0.9.18).



Top comments (0)