Skip to content

Deployment guide (EDOS ecosystem)

Audience: Agent + operator

This guide explains how to deploy apps in this ecosystem in a way that preserves integration contracts (IPC, journal flow, and fair-use API access via cache).

Agent quick path

If you are an agent implementing deployment changes, start here first:

  1. AGENT_DEPLOYMENT_QUICKREF.md
  2. Compose file for production (agents: new apps)
  3. Relevant repo docker-compose.yaml (edam, edjr, api-cache, or docs-deploy)

Scope

  • This is a multi-repo ecosystem, not a monorepo.
  • Deploy each project independently.
  • Keep contracts aligned with ARCHITECTURE.md, IPC.md, and AGENTS.md.

Docker Compose in each app repo

Compose definitions live next to each project, not necessarily in this docs tree:

  • Typical locations: docker-compose.yml or docker-compose.yaml at the repository root of each app/service (sometimes alongside a Dockerfile).
  • Known examples within the ecosystem (path is relative to that repository root):
Project Compose (typical) Notes
elite-dangerous-surface-map docker-compose.yaml Align Traefik/host rules with deployed map URL
elite-dangerous-remote-journal-reader docker-compose.yaml Example Traefik + image for EDJR host
api-cache-server docker-compose.yml (+ Dockerfile) Bun/Elysia cache service
edos docs site (docs-deploy/) docs-deploy/docker-compose.yaml (+ Dockerfile) Static MkDocs site at docs.edos.howfe.org, Traefik websecure + letsencrypt, registry tag docs

Treat those files as the authoritative reference for how each app plugs into Docker and Traefik on the deployment server.

Hosted Markdown documentation (docs.edos.howfe.org)

The edos repository publishes docs in two surfaces on the same host:

Surface Paths Audience
MkDocs Material Site root / (HTML) Humans in browsers; diagrams rendered
/raw/ tree /raw/**/*.md (Markdown, text/markdown) Agents / tools: same files as repo docs/, no theme chrome, stable *.md URLs

For hosted agents, replace __EDOS_DOCS_BASE__ with https://docs.edos.howfe.org (no trailing slash) in templates/CURSOR_AGENT_BOOTSTRAP.md; URLs in that prompt use /raw/....

Path Purpose
docs-deploy/Dockerfile Multi-stage: MkDocs Material build of docs/; COPY docs into /usr/share/nginx/html/raw for agent Markdown; nginx serves / (HTML) + /raw/ (.md) on port 80
docs-deploy/mkdocs.yml Site config (nav + theme + Mermaid fenced blocks via pymdownx.superfences)
docs-deploy/docker-compose.yaml Production Traefik pattern; Host(\docs.edos.howfe.org`)**; image **registry.gitlab.com/elite-dangerous1/edos:docs`
Repository root .gitlab-ci.yml build-docker-docs: push $CI_REGISTRY_IMAGE:docs; deploy-docs: SCP compose to /home/root/projects/edos-documentation/ then docker compose pull && docker compose up -d

DNS: Point docs.edos.howfe.org at the same host Traefik terminates on.

CI/CD: Reuse DEPLOY_SSH_HOST and DEPLOY_SSH_PRIVATE_KEY pattern from elite-dangerous-surface-map.

Registry: If your GitLab project path is not elite-dangerous1/edos, edit the image: line in docs-deploy/docker-compose.yaml to match $CI_REGISTRY_IMAGE:docs.

This workspace checkout may be docs-only. If your edos directory only contains docs/, clone or open each app Git repository separately to edit docker-compose* and deploy.

Prerequisites

  • Node.js/npm for Quasar/Vue apps (elite-dangerous-surface-map, elite-dangerous-remote-journal-reader).
  • Bun for api-cache-server.
  • Container runtime (optional but recommended for production) if using Docker/compose flows.
  • TLS-capable reverse proxy (e.g. Traefik/Nginx) for public endpoints.

Deployment server (Docker host) workflow

This ecosystem is commonly deployed to a single Docker host running multiple app stacks behind a shared reverse proxy (often Traefik).

Because server credentials, hostnames, and paths differ per operator, this section documents a repeatable pattern and leaves environment-specific values as TODO: Verify.

Access

  • SSH: TODO: Verify - how operators authenticate (usernames, SSH keys, VPN, bastion).
  • Docker permissions: ensure your SSH user can run docker / docker compose on the host (docker group or root).

Pick a consistent directory layout on the deployment server so each app can be rolled out/rolled back independently.

Example:

  • /srv/edos/traefik/ (or equivalent reverse-proxy stack)
  • /srv/edos/apps/APP_SLUG/compose.yml
  • /srv/edos/apps/APP_SLUG/.env (host-only secrets and per-environment config)
  • /srv/edos/apps/APP_SLUG/data/ (volumes/bind-mounts, if any)

Rollout procedure (manual compose)

For one app directory on the host:

  1. Put a compose.yml (or docker-compose.yml) + .env in /srv/edos/apps/APP_SLUG/.
  2. Pull the new image(s): docker compose pull
  3. Apply changes: docker compose up -d
  4. Verify:
  5. container health/logs: docker compose ps, docker compose logs --tail=200 -f
  6. reverse proxy routes (Traefik dashboard / curl) are correct
  7. Roll back:
  8. redeploy previous image tag in .env / compose and run docker compose up -d again

TODO: Verify - whether this is manual-only or also performed by GitLab CI on push/tag.

Services

  • Surface map UI: static web app build, served over HTTPS.
  • Remote journal reader UI: static web app build, served over HTTPS (commonly as popup target).
  • API cache server: Bun/Elysia service, served behind reverse proxy.

Baseline URL contract

  • Surface map opens remote journal reader (currently hardcoded in code) at a public URL like https://edjr.YOUR_DOMAIN.
  • Surface map routes third-party API calls through cache server URL like https://api-cache.YOUR_DOMAIN?url=....

If you self-host, keep the same behavior even if hostnames differ.

Hard requirements

  1. Fair-use requirement: all community third-party API requests (SPANSH/EDSM/etc.) must go through the cache server (or an equivalent proxy with caching/queue behavior).
  2. HTTPS everywhere for public pages and API endpoints.
  3. Origin policy aligned with IPC design if you tighten allowedSenderOrigins/allowedListenerOrigins.
  4. No monorepo assumptions in CI/deploy scripts.

Per-project deployment notes

api-cache-server

  • Code path: api-cache-server/api/.
  • Run locally:
  • bun run dev
  • bun run start
  • Container artifacts exist (api-cache-server/Dockerfile, docker-compose.yml) but still treat production setup as TODO: Verify for your environment.
  • Security note: service proxies arbitrary URL query targets; restrict exposure and host allowlists if needed.

elite-dangerous-remote-journal-reader

  • Code path: elite-dangerous-remote-journal-reader/ui/.
  • Build:
  • npm run build
  • Deploy as static site behind HTTPS.
  • Existing repo includes docker-compose.yaml with Traefik labels as a reference deployment.
  • Runtime behavior depends on browser File System Access API and window.opener usage.

elite-dangerous-surface-map

  • Code path: elite-dangerous-surface-map/ui/.
  • Build:
  • npm run build
  • Deploy as static site behind HTTPS.
  • Verify it can reach:
  • remote journal reader URL
  • API cache URL
  • If changing hostnames, update code/config accordingly (currently URLs are hardcoded in source).

Environment and configuration strategy

Current state in this workspace uses hardcoded service URLs in TS files. For maintainable deployments, prefer a configurable base URL strategy:

  • EDJR_BASE_URL
  • API_CACHE_BASE_URL

Then wire builds to inject environment values per deployment target (dev/stage/prod).

Compose file for production (agents: new apps)

Goal: a new app ships a docker-compose.yaml (or .yml) at repository root so an operator can copy that file onto the deployment server, run docker compose pull then docker compose up -d, and Traefik serves it over HTTPS like the existing services.

This section matches the current production pattern used by:

  • elite-dangerous-surface-map/docker-compose.yaml (service edam, host edam.howfe.org, container port 80)
  • elite-dangerous-remote-journal-reader/docker-compose.yaml (service edjr, host edjr.howfe.org, container port 80)
  • api-cache-server/docker-compose.yml (service api-cache, host api-cache.howfe.org, container port 3000)

On the production host, these repository compose files are what is run (copied or checked out there, then docker compose up -d). Values such as entrypoints=websecure, tls.certresolver=letsencrypt, the external network traefik, and the http-redirectscheme labels are not abstract examples: they match the live Traefik defaults on that server. New apps should keep the same Traefik-related labels and only change router/service names, Host(...), image:, and inner container port.

Server prerequisites (before docker compose up -d)

  1. Shared Docker network: an external network named traefik must exist (created by the Traefik stack). Every app compose file ends with:
networks:
  traefik:
    external: true
  1. DNS: the hostname in Host(\...`)` must resolve to this server so Traefik can obtain certificates and browsers can reach the app.

  2. Image availability: image: must reference a registry the server can docker pull (typically registry.gitlab.com/elite-dangerous1/PROJECT_NAME:IMAGE_TAG). TODO: Verify registry login (docker login) on the host for private images.

  3. No host port publishes: existing stacks do not map ports: on the app container. Traefik reaches the backend over the traefik network only.

What you must customize (copy-paste checklist)

Pick a short, unique service key (YAML services.SERVICE_KEY): e.g. edam, edjr, api-cache. For a new app use a new key (not colliding with other compose projects on disk).

Pick Traefik identifiers used in labels - they must be globally unique on this Docker host across all stacks Traefik watches:

Placeholder Meaning Example (surface map)
SERVICE_KEY Compose service name edam
ROUTER_NAME traefik.http.routers.ROUTER_NAME edam-router
BACKEND_SERVICE_NAME traefik.http.services.BACKEND_SERVICE_NAME edam-service
HOST_FQDN Host(...) rule `edam.howfe.org`
CONTAINER_PORT Port the process listens on inside the container (loadbalancer.server.port) 80 for static nginx fronts; 3000 for api-cache Bun

Replace every occurrence consistently:

  • traefik.http.routers.ROUTER_NAME.rule=Host(\HOST_FQDN`)`
  • traefik.http.routers.ROUTER_NAME.entrypoints=websecure
  • traefik.http.services.BACKEND_SERVICE_NAME.loadbalancer.server.port=CONTAINER_PORT
  • Use the same ROUTER_NAME for all traefik.http.routers.ROUTER_NAME.* lines for this app.

Fixed labels (keep as in the repos above; they match production): entrypoints=websecure, tls.certresolver=letsencrypt, plus the http-redirectscheme middleware lines exactly as in edam / edjr / api-cache.

Minimal shape (conceptual)

services:
  SERVICE_KEY:
    image: registry.gitlab.com/elite-dangerous1/PROJECT_NAME:IMAGE_TAG
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ROUTER_NAME.rule=Host(`HOST_FQDN`)"
      - "traefik.http.routers.ROUTER_NAME.entrypoints=websecure"
      - "traefik.http.services.BACKEND_SERVICE_NAME.loadbalancer.server.port=CONTAINER_PORT"
      - "traefik.http.routers.ROUTER_NAME.tls=true"
      - "traefik.http.routers.ROUTER_NAME.tls.certresolver=letsencrypt"
      - "traefik.http.middlewares.http-redirectscheme.redirectscheme.scheme=https"
      - "traefik.http.middlewares.http-redirectscheme.redirectscheme.permanent=true"
    networks:
      - traefik

networks:
  traefik:
    external: true

Operator steps on the deployment server

  1. Create a directory, e.g. /srv/edos/apps/SERVICE_KEY/.
  2. Copy docker-compose.yaml (and optional .env if you introduce one later) into that directory.
  3. Run from that directory:
docker compose pull
docker compose up -d
  1. Confirm: docker compose ps, logs, HTTPS in browser.

Rollback: change :tag on image: to the previous digest/tag, run pull + up -d again.

Workflow for an agent authoring a new app

  1. Add Dockerfile (or CI-built image elsewhere) so CI produces IMAGE_REF the server can pull.
  2. Commit docker-compose.yaml at repo root following the checklist above - copy the Traefik label pattern from an existing app (edam, edjr, or api-cache) and only replace app-specific names, host, image, and port.
  3. CONTAINER_PORT must match EXPOSE / listen port in the image (see api-cache vs static UIs).
  4. Coordinate hostname (Host(...)) + DNS before first deploy.

Valid syntactic starter

Repo-local template (replace exampleapp / hostname / image / port):

TODO: Verify - if operators use compose project names (-p), host layout, or tag policy different from "copy dir + compose up -d" above.

Smoke test checklist after deployment

  1. Open surface map page successfully.
  2. Trigger journal reader popup from map.
  3. In popup, grant file access and start watching Status.json and journal .log.
  4. Confirm surface map receives live status/event updates.
  5. Confirm third-party data loaders resolve via cache server (not direct upstream).
  6. Verify browser console/network has no mixed-content or CORS errors.

Rollback checklist

  1. Keep previous deploy artifact/image available.
  2. Roll back surface map and remote journal reader together if IPC contract changed.
  3. If only cache server changed and clients break, roll back cache service first.
  4. Re-run smoke checklist.

New app deployment checklist (for future repos)

If creating a new app in a different repository:

  1. Keep independent build/deploy pipeline.
  2. Reuse @howfe/elite-dangerous-event-types and/or @howfe/inter-frame-messenger where relevant.
  3. Route community third-party API traffic via cache service.
  4. Add docker-compose.yaml compatible with Compose file for production (agents: new apps) (same Traefik/network pattern as existing apps).
  5. Add a short deployment section in that app's README and link back to this guide.
  6. Add/update docs in ecosystem repo (REPOS.md + apps/APP_SLUG.md).

Known gaps

  • CI/CD specifics per repository remain TODO: Verify.
  • Unified host-allowlist policy for cache target URLs remains TODO: Verify.
  • URL configuration is currently hardcoded in app code and should be externalized.