Temporal Admin Access
The Temporal Web UI is an administrative console for the workflow engine, not a product feature. Hegemony keeps it closed and a platform admin opens it deliberately, on the host, for as long as the investigation takes.
What is closed, and why
Two properties decide the whole design:
- It authenticates nobody. The
temporalio/uiimage ships no login. Anything that can reach its port is an administrator. - It is scoped to no organization. The platform runs a single Temporal namespace and
temporalio/auto-setupships no authorizer plugin, so whoever reaches the console reads every organization's workflow inputs and outputs and can terminate, signal or reset their runs. There is no per-tenant view of it to hand out.
So the console is not a thing to put a role gate in front of — a role gate would hide the link, not the console. It is closed at the layers that actually stop traffic:
| Layer | Posture |
|---|---|
| Container | Behind the temporal-ui Compose profile — a plain up never creates it, in dev, prod or demo |
| Host port | Nothing published by default; when opened, pinned to the literal 127.0.0.1 with no variable that can widen it |
| Reboot | restart: "no", so the Docker daemon never brings back a console someone left open |
| HTTPS overlay | Not proxied. The UI and Keycloak vhosts front services that authenticate their own users; the console does not |
| Application | Run detail does not link to it, and the API returns no console URL |
The Temporal server's own gRPC frontend (7233) and its database are internal to the Docker network in production, and loopback-only in dev.
Tier 0: tctl inside the container
The cheapest path, and the one to reach for first: it opens no socket at all and answers most "what is this workflow doing?" questions.
# dev
task compose:dev:exec -- temporal tctl --address temporal:7233 --ns default workflow describe --workflow_id <workflow-id>
# prod
task compose:prod:exec -- temporal tctl --address temporal:7233 --ns default workflow describe --workflow_id <workflow-id>
# demo
task compose:demo:exec -- temporal tctl --address temporal:7233 --ns default workflow describe --workflow_id <workflow-id>The explicit --address is not optional: the compose files leave BIND_ON_IP unset, so the frontend binds the container's network address rather than 127.0.0.1, which is where a bare tctl connects. temporal:7233 is the Compose service address — the same one both healthchecks use — and it resolves from inside the container. Do not write $(hostname -i) here: your own shell expands it before task runs, so tctl would be handed the host's address. Temporal Retention & History Limits carries the retention and dynamic-config recipes.
Get the workflow ID from the run: as a platform admin, open the run in Hegemony and use Show Temporal identifiers in the run header, which reveals the workflow ID and the Temporal run ID with a copy button.
Tier 1: the console, on demand
When the history graph is genuinely what you need:
Each stack has its own task, so there is no way to open the wrong console by forgetting an environment variable:
task compose:temporal:ui:dev
task compose:temporal:ui:prod
task compose:temporal:ui:demoThe task runs the console in the foreground with --rm and publishes it on 127.0.0.1 only. Its lifetime is your terminal: Ctrl-C closes it and removes the container. Nothing survives a reboot, and the next task compose:*:down sweeps the profile as well.
It prints the address to use, reading TEMPORAL_UI_PORT from that stack's own env file — 8080 for dev and prod, 8088 for the demo, which has to dodge the demo UI on 8080. Substitute your own value below if you have overridden it.
On a remote host, forward the loopback port rather than widening the bind:
ssh -N -L 8080:127.0.0.1:8080 <user>@<host> # 8088 for the demo stackThen open http://127.0.0.1:8080 in your own browser.
Close it when you are done. The console has no session, no audit trail of its own, and no tenant boundary, so an open one is a standing cross-tenant administrative door.
Never route the console through the
httpsoverlay or any other reverse proxy without putting authentication on that vhost. It previously had one, on0.0.0.0, with none — which is the exposure this design removes.
What platform admins get in the product instead
- Run detail → Show Temporal identifiers — the workflow ID and Temporal run ID for the run in front of you, ready to paste into
tctlor the console's search box. - Home → platform health — Temporal reachability and task-queue depth, without touching the console at all. See Home.
Checking the posture
# No console container, in any state
docker compose ... ps -a --services | grep -qx temporal-ui && echo "OPEN" || echo "closed"
# No Temporal port on the host
ss -ltn | grep -E ':(8080|8088|5445)\b' || echo "no temporal port published"COMPOSE_PROFILES must stay unset in your .env files: Compose reads COMPOSE_* from --env-file, so a single COMPOSE_PROFILES=temporal-ui line would re-open the console on every up, permanently and with no other signal.
Note for air-gapped hosts
docker compose pull skips services whose profile is not enabled, so temporalio/ui is no longer pre-pulled with the rest of the stack. The first task compose:temporal:ui:* pulls it — pre-pull the image if the host has no registry access.