Skip to content

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.