Configuration
Environment variables
| Var | Used by | Notes |
|---|---|---|
MINMCS_MISSION | api.py | Mission name — db filename + display label (default minscs). |
MINMCS_XTCE | importers.py | Path to the XTCE file the model is built from; if unset, searches upward from CWD for minscs.xtce. |
MINMCS_DATABASE (or MINMCS_DB_URL) | core.py | SQL connection string; default sqlite:///<mission>.db. MINMCS_DATABASE takes precedence, MINMCS_DB_URL is kept for back-compat. |
MINMCS_TC_SCHEDULING | core.py | Opt in to on-board execution times (1/true/yes/on). Also needs the model to define a schedule-insert command — see Telecommand uplink. |
MINMCS_LINK | bin/data_proxy.py | Ground link type; only EGSE is currently supported. |
MINMCS_TM_SOURCE_HOST / _PORT | bin/data_proxy.py, bin/egse_link.py | TM connection the bridge opens (default localhost:9310). |
MINMCS_TC_TARGET_HOST / _PORT | bin/data_proxy.py, bin/egse_link.py | TC connection the bridge opens (default <tm host>:9311). Match this to MINMCS_TM_SOURCE_HOST/_PORT for the real EGSE tool — see Ground link bridge. |
MINMCS_API_URL | bin/data_proxy.py | API base URL the bridge talks to (default http://localhost:9300). Compose-only — not set in the committed .env.example, since the bridge’s own default already works both for non-Docker local runs and for the host-networked minmcs-data-proxy container. |
MINMCS_INFLUXDB_URL / _TOKEN / _ORG / _BUCKET | telemetry.py | All four required to enable time-series (and, effectively, relational parameter) storage — see Telemetry ingest. |
VITE_MINMCS_API | UI build/dev | Backend base URL, default http://localhost:9300. Read from the root .env by docker-compose.yaml and passed to the UI image as a build arg — build time, not run time, so changing it needs a rebuild. |
Pointing the UI at a different API host
VITE_MINMCS_API is resolved in the operator’s browser, not inside the compose network.
So it has to name an address that’s reachable from wherever the dashboard is opened — the
host’s name or IP, never the internal minmcs-api:9300. Set it in the root .env:
VITE_MINMCS_API=http://mcs-host:9300Or override it for a single invocation (the shell environment wins over .env):
VITE_MINMCS_API=http://mcs-host:9300 docker compose up --buildVite inlines this value into the bundle at npm run build, so the --build matters: a
plain docker compose up reuses whatever image was already compiled with the old URL. To
rebuild just the UI, run docker compose build minmcs-ui && docker compose up -d minmcs-ui.
Building with Docker
$ docker compose up --buildThis runs five services, one container each. Four of them share the minmcs-network bridge;
minmcs-data-proxy is the exception (see below).
| service | role | port |
|---|---|---|
minmcs-postgres | TimescaleDB (packettm/packettc/paramtm tables) | — |
minmcs-api | FastAPI (Dockerfile.minmcs-api, uv-managed) | 9300 |
minmcs-data-proxy | the ground-link bridge. Same image as minmcs-api, but with its entrypoint overridden to bin/data_proxy.py. Runs with network_mode: host | — |
minmcs-ui | built SPA behind nginx (Dockerfile.minmcs-ui, two-stage: npm run build then nginx:stable-alpine) | 8080 |
minmcs-influxdb | optional time-series store | 8086 |
containers/nginx/default.conf proxies /ws and /api/ to minmcs-api:9300, and serves the
built SPA for everything else (try_files ... /index.html, history-mode routing).
Why the bridge uses host networking
The real EGSE ground-link tool binds its socket to 127.0.0.1 only. That’s
INADDR_LOOPBACK, hardcoded in its own egse/egse.c, with no flag to change it. A
loopback-bound socket is unreachable from a bridged container at any address:
host.docker.internal resolves fine to the docker0 gateway, but the connection itself then
fails with ECONNREFUSED. The bridge reports that as:
[proxy] tm downlink connection failed: [Errno 111] Connection refusedSo minmcs-data-proxy runs in the host’s network namespace (network_mode: host) instead of
the bridge network, and dials the ground link exactly as a host-side
uv run python bin/data_proxy.py would — localhost:21001 with the repo’s example .env
(the code’s own built-in fallback, if nothing is configured at all, is 9310/9311 — see the
environment variables table above).
Two consequences follow from host networking:
- The compose DNS name
minmcs-apidoesn’t resolve for this service, so itsMINMCS_API_URLhas to point at the API’s published port instead:http://localhost:9300. - Host networking is a Linux feature. On Docker Desktop,
minmcs-data-proxyneeds a ground link that listens on a routable address instead of loopback-only.
Expect a few POST failed (ConnectionError) lines at startup — the bridge comes up before the
API is listening, and retries with backoff.
Three named volumes — minmcs-postgres-data, minmcs-influxdb-data, and
minmcs-influxdb-config — outlive docker compose down (only -v removes them). This
matters after a schema change: there’s no migration tooling, and
SQLModel.metadata.create_all only creates missing tables — it never adds columns to an
existing one. So after a schema change, either drop the volumes (losing data) or add the new
columns by hand, e.g.:
$ docker exec minmcs-postgres psql -U postgres -d minmcs \
-c "ALTER TABLE packettc ADD COLUMN IF NOT EXISTS <col> <type>"Get the exact Postgres type from the SQLModel column definition rather than guessing it:
CreateTable(PacketTC.__table__).compile(dialect=postgresql.dialect()). The same idea applies
to the SQLite file used outside Docker, but simpler — just delete it and let create_all
rebuild it, since there’s nothing in it worth migrating.