The Mimir service stack: FastAPI backend (mimir-api/), React web UI
(mimir-web/mimir-ui/), and mDNS discovery (mimir-discovery/), orchestrated
with Docker Compose and Task.
- Which stack do I run?
- Prerequisites
- Running locally
- Production deployment
- MQTT broker & auth
- Configuration
- Common tasks
- Database
- Troubleshooting
- Notes & limitations
| Situation | Command | Compose file |
|---|---|---|
| Local dev, browser/API work (WSL or Linux) | task wsl:up:build |
docker-compose.wsl.yml |
| Local dev with hot reload (API + UI) | task dev |
docker-compose.dev.yml |
| Production from source on a native Linux server | task up:build |
docker-compose.yml |
| Production from released images (recommended) | see mimir-release | mimir-release/deploy/ |
The production compose uses host networking and an authenticated MQTT
broker — it will refuse to start without mosquitto/passwd (generated by
scripts/phase0_harden.sh). The local/dev stacks use bridge networking and an
anonymous dev broker (mosquitto/mosquitto.dev.conf); they are not valid for
real-device onboarding (mDNS/multicast doesn't survive WSL/bridge networking).
- Docker + Docker Compose (on Windows: Docker Desktop running, with WSL integration enabled for your distro)
- Task:
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/binRun task from this directory to list all available commands.
cp .env.example .env # browser-only local use: PUBLIC_HOST can stay blank
task setup # installs API .venv + UI node_modulesRun
task setupbefore starting any Docker dev stack the first time — otherwise Docker creates a root-ownednode_modulesmountpoint and hostnpminstalls fail withEACCES(see Troubleshooting).
Full stack in containers, served on localhost. Best for using the app locally and for API work where you don't need live reload.
task wsl:up:build # build + start
task health:wsl # check the API responds
task wsl:down # stopFastAPI with --reload and the CRA dev server with file watching. Best for
active development on API or UI code.
task dev # start full dev stack
task dev:logs:api # follow API logs only
task dev:down # stop
task dev:reset # wipe volumes and start fresh (asks for confirmation)
task dev:discovery # optional: mDNS discovery sidecar (Linux/WSL only)pgAdmin (optional, either stack):
docker compose -f docker-compose.dev.yml --profile tools up -d --build # http://localhost:5050| What | Local stack (A) | Dev mode (B) |
|---|---|---|
| Web UI | http://localhost:8080 | http://localhost:3000 |
| API | http://localhost:5000 | http://localhost:5000 |
| API health | http://localhost:5000/api/health | same |
| Postgres | localhost:5432 | same |
| Redis | localhost:6379 | same |
| MQTT (anonymous, dev) | localhost:1883 | same |
When running on a native Linux server, Mimir advertises itself as mimir.local
via mDNS. Other devices on the network can reach it at:
| What | URL |
|---|---|
| Web UI | http://mimir.local:8080 |
| API | http://mimir.local:5000 |
No IP address configuration is needed for most setups. PUBLIC_HOST in .env
is only required if mDNS doesn't reach your displays (e.g. separate VLANs).
Recommended: deploy released GHCR images with the auto-update timer from
mimir-release — see its README.
The server then converges to the pinned versions in versions.yml every
15 minutes; releasing is a pin-bump PR.
From source (native Linux server with displays on the LAN):
cp .env.hybrid.example .env # PUBLIC_HOST optional — mDNS handles LAN discovery
bash scripts/phase0_harden.sh # secrets, mosquitto passwd, hardened restart
task up:buildDetails and the discovery-service setup: HYBRID_LINUX_DEPLOYMENT.md.
Production (mosquitto/mosquitto.conf) runs two listeners:
- 1883 on 0.0.0.0 — LAN listener for displays. Requires credentials from
mosquitto/passwd(gitignored; generated byscripts/phase0_harden.sh). Displays receive the credentials automatically during bootstrap when the API hasMQTT_EXPOSE_CREDENTIALS=true. - 1884 on 127.0.0.1 — anonymous localhost listener used by the
host-networked API, the compose healthcheck, and
task mqtt:sub/mqtt:pub.
Dev/WSL stacks use mosquitto/mosquitto.dev.conf: anonymous on 1883, no
password file, bridge networking.
.env.example— local/WSL development (PUBLIC_HOST can stay blank).env.hybrid.example— production server template (PUBLIC_HOST optional — mDNS handles LAN discovery; set it only for multi-VLAN or non-mDNS environments)mimir-api/mimir-api.docker.env— Docker defaults for the API (no secrets); composeenvironment:entries override it- The web UI auto-detects the API URL at runtime and can be overridden in the
Settings page;
REACT_APP_API_URLis only needed for unusual setups.
# Database
task db:migrate # run migrations in the dev API container
task db:revision -- 'add x' # generate a new Alembic migration
task db:psql # psql shell on the dev DB
# Code quality
task api:lint # ruff + mypy
task api:format
task api:test # pytest
# Physical displays (dev loop; production updates go through OTA)
task display:deploy -- pi@display-pi-01.local # rsync ../mimir-display + restart
task display:logs -- pi@display-pi-01.local
task display:restart -- pi@display-pi-01.local
# MQTT debugging (localhost listener)
task mqtt:sub # subscribe to mimir/#
task mqtt:pub -- topic msg
# Health
task health # local API
task health:prod # server at PUBLIC_HOSTPostgres 16 runs as the db service, bound to 127.0.0.1:5432.
Credentials: user mimir, database mimir; password from POSTGRES_PASSWORD
in .env (dev default: mimir). The API runs alembic upgrade head
automatically on startup.
npm ci fails with EACCES: permission denied, mkdir .../node_modules/...
The Docker dev stack created node_modules as a root-owned volume mountpoint
before a host-side install existed. Fix:
sudo rm -rf mimir-web/mimir-ui/node_modules && task ui:installtask up:build fails: mosquitto/passwd is missing
You're running the production compose. For local work use task wsl:up:build.
On a real server, run bash scripts/phase0_harden.sh first. If a previous
attempt left a mosquitto/passwd directory: sudo rm -rf mosquitto/passwd.
unable to get image ...: cannot connect to the docker API
The Docker daemon isn't reachable from WSL. Docker Desktop: start it and enable
WSL integration for your distro (Settings → Resources → WSL Integration).
Native engine: sudo systemctl start docker (or sudo service docker start).
Discovery finds 0 devices on Windows/WSL Expected — multicast doesn't traverse WSL/bridge networking. Validate onboarding on a native Linux server. For Windows-host discovery experiments see WINDOWS_MDNS_FIREWALL.md and MDNS_EDGE_DISCOVERY.md.
- A native Linux server is the reference environment for real-device onboarding (host-network mDNS). Develop locally; validate hardware there.
- Dev channel linking (Developer Mode): the path you enter must exist inside
the API container. The dev compose mounts the sibling channel repos at
/plugins/<repo-name>, e.g./plugins/image-frame-channel-mimir/channels/photo_frame. Linked dev channels are watched (polling) and reload automatically. - The web UI's optional PWA/service worker is disabled by default for local
Docker (a stale SW can serve the offline page); enable intentionally with
REACT_APP_ENABLE_PWA=trueat build time. - pgAdmin and the DB/Redis ports are bound to
127.0.0.1— use an SSH tunnel for remote access, e.g.ssh -L 5050:127.0.0.1:5050 user@server.
Mimir is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
See LICENSE for the full text.
Free for: personal use, non-profits, and open source projects (under AGPL terms).
Commercial license: If you want to use Mimir in a proprietary product or service
without the AGPL's source-sharing obligations, a commercial license is available.
See COMMERCIAL_LICENSE.md or contact contact@mimirframe.com.
Contributing: By submitting a pull request you agree to the terms in CLA.md,
which allows Mimir to be offered under both open source and commercial licenses.
Copyright (C) 2026 Ryan Lane
