0017. Proxy the VictoriaTraces UI through nginx¶
Date: 2026-09-03 Status: Accepted
Context¶
ADR-0011 published the VictoriaTraces
container's port directly to the host (ports: ["10428:10428"]), so the built-in UI was reached at
http://localhost:10428/select/vmui — deliberately not behind nginx, on the grounds that
VictoriaTraces carries no session cookies or app-domain concerns that would call for TLS or a
conflux.local subdomain, which made a plain published port the simplest option.
That premise still holds on its own terms. What it did not account for is parallel stacks. Since
then, setup-worktree-env.sh gives each git worktree its own Compose stack, kept apart by two
variables: COMPOSE_PROJECT_NAME scopes every container, network and volume name, and APP_HOST
scopes every hostname nginx serves. A published host port is the one thing neither can scope,
because the host's port space is shared across all stacks.
So the port had to be scoped by hand. The script wrote VICTORIA_TRACES_PORT=10429 for a worktree
running beside the main checkout, and a VICTORIA_TRACES_PORT_OVERRIDE variable existed so a third
concurrent stack could be handed yet another free port. That is per-developer bookkeeping that grows
with the number of worktrees, for the only service in the stack that needed it.
Everything else a browser reaches already goes through nginx under APP_HOST:
mq.${APP_HOST} for the RabbitMQ management UI, docs.${APP_HOST} for the Zensical docs, and
${APP_HOST} for the application. None of them needs port coordination. Measured against a single
stack, the published port was the simpler option; measured against parallel stacks, it is the more
complex one.
Decision¶
The VictoriaTraces UI is reached at traces.${APP_HOST}, proxied by nginx, and the
victoria-traces service publishes no host port at all.
nginx gains a traces.${APP_HOST} server block that terminates TLS with the existing mkcert
wildcard certificate and proxies to http://victoria-traces:10428. It follows the shape of the
mq.${APP_HOST} block — a plain proxy_pass — rather than the resolver-plus-variable
indirection the docs.${APP_HOST} block uses, which exists for an upstream that restarts on its
own; VictoriaTraces is a long-running service like RabbitMQ. The service joins nginx's depends_on,
and traces.${APP_HOST} joins its network aliases so docker-hoster resolves the name on the host.
Trace ingestion is unaffected and does not pass through nginx: the exporter keeps POSTing to
http://victoria-traces:10428/insert/opentelemetry/v1/traces over the internal Docker network. The
proxy carries the human-facing UI only, never the trace path.
VICTORIA_TRACES_PORT and VICTORIA_TRACES_PORT_OVERRIDE are removed from
setup-worktree-env.sh and .env.example.
Consequences¶
No service in the stack publishes a host port any more. The number of Compose stacks that can run
side by side is therefore no longer bounded by hand-picked ports — COMPOSE_PROJECT_NAME and
APP_HOST scope everything a stack owns, and adding a worktree needs no port decision.
The UI URL changes from http://localhost:10428/select/vmui to
https://traces.${APP_HOST}/select/vmui. It now shares the two prerequisites the other proxied UIs
already have: docker-hoster must run before the containers start, and ./install.sh must have
generated the nginx config and TLS certificate. Because conf.d is baked into the nginx image at
build time, picking up this change requires docker compose build nginx, not just a pull.
An existing worktree .env keeps an orphaned VICTORIA_TRACES_PORT line. It is inert — nothing
reads it any more — and setup-worktree-env.sh no longer rewrites it.
This supersedes only how the UI is reached. The choice of VictoriaTraces as the trace backend, the opt-in export behaviour, the internal OTLP endpoint, and everything in ADR-0011's Consequences remain in effect.