Skip to content

On-premise installation ​

Self-host designIQ, collaborative modeling and architecture with AI, with Docker. One container runs the Live Host: Hocuspocus document sync (WebSocket), the REST API, the collaborative web app, and the MCP endpoint for AI clients, all on one port. Releases become pull requests in your own GitHub organization; review + merge stays where it is today. The platform never executes code from a content repository.

The auth model in one line: login authenticates, repositories authorize. Users sign in at your identity provider (OIDC), and per-(user,repo) write permission is checked through your own GitHub App's installation token. The server stores zero user tokens (ADR 0001, ADR 0007).

Companion documents:

Prerequisites ​

  • A GitHub organization (or GitHub Enterprise) holding your content repositories, each a root designiq.yml naming the folder its models live in.
  • An OIDC identity provider, the one login (ADR 0007). None yet? The compose file ships a Keycloak with a ready realm (idp-quickstart.md); evaluating without any login is LIVE_AUTH=none.
  • Docker with the compose plugin.
  • A public HTTPS URL for the instance: the OAuth callback and webhook URLs derive from LIVE_PUBLIC_URL. A localhost evaluation works without one (webhooks simply can't reach you; the install-redirect fallback covers repo connection).
  • Outbound HTTPS (443) to github.com and api.github.com (or your GHE hosts) for the API and for git clone/fetch/push. No SSH access is needed; all git traffic is HTTPS.

The image ​

ghcr.io/miragon/designiq-live-host, multi-arch (linux/amd64 + linux/arm64), built from apps/live-host/Dockerfile by the release workflow (see ADR 0004 for the artifact flow: the same image serves on-prem installs and Miragon's hosted cells).

TagMeaning
latestlatest release; pin vX.Y.Z for production
vX.Y.Za specific release
edgelatest main build
sha-<full-sha>exact commit build (full sha, for pin-mapping)

The compose file takes the tag from DESIGNIQ_VERSION in .env (default latest).

Container facts: listens on PORT=8080, state under LIVE_DATA_DIR=/data (mount a volume there), ships git + CA certificates for cloning connected repos. The container currently runs as root so a freshly mounted volume is writable out of the box. If your policy requires a non-root user, run with user: and make /data writable for that uid.

Quickstart (compose) ​

  1. Register your GitHub App: github-app-setup.md. You come back with an app id, slug, private key, and webhook secret.

  2. Decide on the identity provider (idp-quickstart.md): the shipped Keycloak (a compose profile, its LIVE_OIDC_* values are in .env.example) or your own, via the LIVE_OIDC_* block in configuration.md. Evaluating without an IdP: LIVE_AUTH=none, see the operating modes below.

  3. Configure, then start (the Live Host refuses to boot without its login configured, so write .env before the first up; --profile keycloak adds the shipped Keycloak):

    bash
    cd deploy
    cp .env.example .env        # fill in LIVE_PUBLIC_URL + the GitHub App values
    docker compose up -d

    The compose file is deploy/docker-compose.yml; the annotated env template is deploy/.env.example. It sets stop_grace_period: 30s so the graceful-shutdown flush (LIVE_SHUTDOWN_MS, 25 s) can persist debounced write-throughs before the container is killed. Keep that headroom if you write your own unit files.

  4. Put your reverse proxy in front (below), open https://<your-host>, sign in at your identity provider, connect repositories via GitHub's install picker. Done.

Plain docker run works too:

bash
docker run -d --name designiq -p 8080:8080 -v designiq-data:/data \
  --env-file .env --stop-timeout 30 ghcr.io/miragon/designiq-live-host:latest

Operating modes ​

The server wires itself from the credentials it finds (configuration.md has every variable):

ModeCredentialsWhat you get
No auth (LIVE_AUTH=none)noneServes a locally mounted content checkout (LIVE_HOST_CONTENT_DIR), or with App credentials the App's repositories, to everyone as the local principal (LIVE_LOCAL_USER). Local evaluation / trusted single-team network only: no login, no authorization (ADR 0007).
OIDC + GitHub App (recommended)LIVE_OIDC_* + App id + private keyInstallation enumeration = multi-repo overview, per-(user,repo) authorization via installation tokens, bot-authored release PRs (ADR 0001), direct webhooks.

Run on-prem in OIDC + GitHub App mode: oidc refuses to start without both. There is also a cell mode (extra TENANT_*/CELL_* variables) used by Miragon's hosted multi-tenant operation (ADR 0002). Leave all of those unset; the server then runs standalone.

AI clients (MCP) ​

The same container serves an MCP endpoint at POST /mcp: agents read and edit the live models under the same per-(user,repo) authorization as the web app:

bash
claude mcp add --transport http designiq https://<your-host>/mcp \
  --header "Authorization: Bearer <token>"

The token: in production, an OIDC access token from your identity provider. Set the LIVE_OIDC_* variables (configuration.md); a LIVE_AUTH=none host needs no token at all (drop the header). Set LIVE_MCP_READONLY=1 to serve the read tools only, LIVE_MCP_BPMN_EDIT=1 to add the semantic BPMN tools (ADR 0009). Tool list and semantics: docs/mcp-integration.md.

Reverse proxy / TLS ​

One port carries plain HTTP and the WebSocket upgrade (Hocuspocus rides the same server). A proxy that doesn't pass the upgrade through is the #1 misconfiguration: the app loads, but documents never sync.

Caddy, where upgrade pass-through is automatic:

design.example.com {
    reverse_proxy localhost:8080
}

nginx, which needs HTTP/1.1 and the Upgrade/Connection headers explicitly:

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    server_name design.example.com;
    # ssl_certificate ...; ssl_certificate_key ...;

    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 1h;   # long-lived WebSocket connections
    }
}

LIVE_PUBLIC_URL must be the public HTTPS URL: session cookies are only marked Secure when it starts with https, and OAuth callback + webhook URLs derive from it.

One cap to know about: the per-IP WebSocket limit (LIVE_MAX_WS_PER_IP, default 40) identifies clients by the Fly-Client-IP header, falling back to the socket address. Behind your proxy that fallback is the proxy's address, so all users share one bucket. Either set the header at the proxy (proxy_set_header Fly-Client-IP $remote_addr;, safe since clients can only reach the app through the proxy) or raise the limit.

Data & backup ​

Everything lives under /data:

PathWhatBackup?
/data/live.dbSQLite: live document lineages (unreleased edits, the crown jewels), sessions, repo registry, token cacheYes
/data/workspaces/<owner>/<repo>git checkouts of connected repos, cloned on demandNo, re-cloneable, safe to exclude

Backup: sqlite3 /data/live.db ".backup /backups/live.db" (consistent while the server runs) or snapshot the whole volume. Restore: restore /data, start the container: lineages, sessions, and the registry come back; workspaces re-clone as needed.

Released state is safe in GitHub regardless; only unreleased live edits depend on live.db. Frequent, small releases keep that exposure short.

Per-user rows. Besides the session, live.db keeps two non-credential tables per person (ADR 0001 as amended): favorites (the repositories someone marked as favorites) and repo_visits (the repositories they opened last, at most 20, none older than 90 days). Both are keyed <provider>:<login> in lower case, such as oidc:petra for an IdP login. Deleting one person's rows (e.g. on a data-subject request):

bash
sqlite3 /data/live.db "DELETE FROM favorites WHERE user = 'oidc:petra'; DELETE FROM repo_visits WHERE user = 'oidc:petra';"

repo_activity holds when each repository last changed (a live edit, its default branch), the same for everyone, no personal data. With LIVE_AUTH=none every request is the one local principal, so favorites and recently opened repositories are shared host-wide (local:<login>).

Upgrades ​

bash
docker compose pull && docker compose up -d

Pull the new tag and recreate. SIGTERM triggers the graceful flush, /data carries the state across. Releases are semver-tagged; breaking changes (env, data layout) are called out in the release notes. For production, pin vX.Y.Z and move deliberately. Coming from 4.x: upgrading-to-5.md. The image name changed with the rename to designIQ, so pull alone does not reach 5.0.

Health ​

GET /healthz: liveness plus a deep check: SQLite writability and disk headroom (<5 % free = degraded). 200 with status: "ok", or 503 with status: "degraded":

json
{ "status": "ok", "tenant": null, "liveDocs": 3, "sqlite": "ok", "diskFreeMb": 51234, "diskUsedPct": 37 }

Standalone (on-prem) the full detail is public: a single-tenant box has nothing to gate. Only in cell mode is the detail reduced to { "status": ... } unless the poller presents the cell secret as a bearer token. Point your monitoring at the status code.