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:
- AGENT_DEPLOYMENT_QUICKREF.md
- Compose file for production (agents: new apps)
- Relevant repo
docker-compose.yaml(edam,edjr,api-cache, ordocs-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.ymlordocker-compose.yamlat the repository root of each app/service (sometimes alongside aDockerfile). - 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 composeon the host (dockergroup or root).
Recommended on-host layout¶
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:
- Put a
compose.yml(ordocker-compose.yml) +.envin/srv/edos/apps/APP_SLUG/. - Pull the new image(s):
docker compose pull - Apply changes:
docker compose up -d - Verify:
- container health/logs:
docker compose ps,docker compose logs --tail=200 -f - reverse proxy routes (Traefik dashboard / curl) are correct
- Roll back:
- redeploy previous image tag in
.env/ compose and rundocker compose up -dagain
TODO: Verify - whether this is manual-only or also performed by GitLab CI on push/tag.
Deployment model (recommended)¶
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¶
- 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).
- HTTPS everywhere for public pages and API endpoints.
- Origin policy aligned with IPC design if you tighten
allowedSenderOrigins/allowedListenerOrigins. - No monorepo assumptions in CI/deploy scripts.
Per-project deployment notes¶
api-cache-server¶
- Code path:
api-cache-server/api/. - Run locally:
bun run devbun 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.yamlwith Traefik labels as a reference deployment. - Runtime behavior depends on browser File System Access API and
window.openerusage.
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_URLAPI_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(serviceedam, hostedam.howfe.org, container port 80)elite-dangerous-remote-journal-reader/docker-compose.yaml(serviceedjr, hostedjr.howfe.org, container port 80)api-cache-server/docker-compose.yml(serviceapi-cache, hostapi-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)¶
- Shared Docker network: an external network named
traefikmust exist (created by the Traefik stack). Every app compose file ends with:
networks:
traefik:
external: true
-
DNS: the hostname in
Host(\...`)` must resolve to this server so Traefik can obtain certificates and browsers can reach the app. -
Image availability:
image:must reference a registry the server candocker pull(typicallyregistry.gitlab.com/elite-dangerous1/PROJECT_NAME:IMAGE_TAG). TODO: Verify registry login (docker login) on the host for private images. -
No host port publishes: existing stacks do not map
ports:on the app container. Traefik reaches the backend over thetraefiknetwork 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=websecuretraefik.http.services.BACKEND_SERVICE_NAME.loadbalancer.server.port=CONTAINER_PORT- Use the same
ROUTER_NAMEfor alltraefik.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¶
- Create a directory, e.g.
/srv/edos/apps/SERVICE_KEY/. - Copy
docker-compose.yaml(and optional.envif you introduce one later) into that directory. - Run from that directory:
docker compose pull
docker compose up -d
- 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¶
- Add
Dockerfile(or CI-built image elsewhere) so CI producesIMAGE_REFthe server can pull. - Commit
docker-compose.yamlat repo root following the checklist above - copy the Traefik label pattern from an existing app (edam,edjr, orapi-cache) and only replace app-specific names, host, image, and port. CONTAINER_PORTmust matchEXPOSE/ listen port in the image (seeapi-cachevs static UIs).- 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¶
- Open surface map page successfully.
- Trigger journal reader popup from map.
- In popup, grant file access and start watching
Status.jsonand journal.log. - Confirm surface map receives live status/event updates.
- Confirm third-party data loaders resolve via cache server (not direct upstream).
- Verify browser console/network has no mixed-content or CORS errors.
Rollback checklist¶
- Keep previous deploy artifact/image available.
- Roll back surface map and remote journal reader together if IPC contract changed.
- If only cache server changed and clients break, roll back cache service first.
- Re-run smoke checklist.
New app deployment checklist (for future repos)¶
If creating a new app in a different repository:
- Keep independent build/deploy pipeline.
- Reuse
@howfe/elite-dangerous-event-typesand/or@howfe/inter-frame-messengerwhere relevant. - Route community third-party API traffic via cache service.
- Add
docker-compose.yamlcompatible with Compose file for production (agents: new apps) (same Traefik/network pattern as existing apps). - Add a short deployment section in that app's README and link back to this guide.
- 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.