Skip to content

Monolith (single jar)

The monolith is the entire platform in one artifact: every service in a single JVM, the dashboard served from the same port as the API, and probes executed by an embedded Lace executor instead of external agents. It needs a PostgreSQL database, a Redis instance, a Java 17-or-newer runtime — and nothing else. It applies its own schema migrations on boot.

It exists because eight services is the right shape for a platform and the wrong shape for a first install. If you are monitoring a handful of endpoints for one team, the operational surface of the full stack — nine containers, a migrator, an agent enrolment — buys you nothing you will use.

The trade, stated plainly

The monolith trades the microservice properties for deployment simplicity, and you should know what you are giving up:

  • No independent scaling. You cannot add scheduler replicas or move ingestion to a bigger box; there is one process and it does everything.
  • No isolation. One component's failure or memory pressure is everyone's. A JVM crash takes the API, the scheduler, and the WebSocket down together.
  • No rolling upgrades of a single piece. Upgrading anything means restarting everything.
  • One vantage point. Probes run inside the monolith's own process. There are no probe agents to place in other regions or networks, and the Agents section of the UI is hidden accordingly — along with the per-service probe mode and agent selection, which only mean something when there is a fleet to select from.

None of this matters for a small installation, and the operational simplicity wins. When it starts to matter, the exit is cheap: the per-service deployment is the same code and the same schema — point it at the same database and Redis and switch.

Running it

Download tracedown-monolith-<version>-all.jar from the backend releases. The published jar has the matching frontend release baked in, so the dashboard is served from the gateway port with no separate frontend deployment.

DATABASE_URL=jdbc:postgresql://localhost:5432/tracedown \
DATABASE_USER=tracedown \
DATABASE_PASSWORD=change-me \
REDIS_A_URL=redis://localhost:6379 \
java -jar tracedown-monolith-<version>-all.jar

On an empty database it migrates the schema and initializes the internal certificate authority. It does not create an account: SINGLE_ORG_MODE is off by default, and it is the only thing in Tracedown that creates a user. Add it to the command above for the first start:

SINGLE_ORG_MODE=true \
DATABASE_URL=jdbc:postgresql://localhost:5432/tracedown \
 \
java -jar tracedown-monolith-<version>-all.jar

That bootstraps a default organization owned by DEMO_USER_EMAIL / DEMO_USER_PASSWORD, which default to the published [email protected] / Down2trace!. Open http://localhost:20714 and log in. Services you create begin probing on their schedule immediately — there is no agent to enrol.

Once you are in, drop the flag: it only ever acts on an empty user table, so leaving it set does nothing, but it is one less thing to reason about.

In production, set real bootstrap credentials or it will not start

Under DEPLOYMENT_ENV=production — which you should be setting, see below — SINGLE_ORG_MODE=true with either credential still on its published value is a startup failure, not a warning, and the password must pass the password policy. There is no override; ALLOW_INSECURE_DEV_KEYS does not lift it. Set both, start once, sign in, then turn the flag back off. See The first account.

Set real secrets before this reaches a network

Like the development Compose stack, the monolith boots with development defaults when secrets are unset — including the platform encryption key. Set PLATFORM_AES_KEY (64 hex chars — generate with openssl rand -hex 32, and set it before you have data worth keeping, because only part of what it encrypts can be moved to a new key later), JWT_SECRET, and DEPLOYMENT_ENV=production, which makes startup refuse placeholder secrets instead of running with them. Work through Secrets & Encryption.

Environment

The monolith is configured entirely by environment variables — the same ones the individual services read (the full reference is in Configuration), because internally it is those services. The variables specific to running them in one process:

Variable What Default
DATABASE_URL JDBC URL, jdbc:postgresql://… required
DATABASE_USER / DATABASE_PASSWORD Postgres credentials required / ""
REDIS_A_URL Operational Redis required
REDIS_B_URL Cache Redis falls back to REDIS_A_URL
GATEWAY_PORT API + dashboard 20714
REALTIME_PORT WebSocket 20870
METRICS_PORT Prometheus scrape endpoint 20850
WS_URL Overrides the WebSocket URL handed to the browser — set /ws behind a reverse proxy direct to the realtime port
STORAGE_FILESYSTEM_ROOT Saved response bodies /data/bodies

The shared PORT variable that platform-as-a-service hosts inject is deliberately ignored: with every service in one process it cannot mean anything.

The remaining services bind their usual ports (2081020870) inside the process. Nothing but the three in the table needs to be reachable — keep the rest firewalled as you would any internal port.

Each of those ports still answers /ping and /health for the service behind it, which is how you tell which embedded component is unhappy in a process that shares one exit code. See Monitoring Tracedown.

In a container

The jar runs fine in a stock JRE image; there is no dedicated monolith image. It is compiled to Java 17 bytecode, so any JRE from 17 up will run it — the tag below is the one the rest of the stack is built and tested on.

docker run -d --name tracedown \
  -p 127.0.0.1:20714:20714 -p 127.0.0.1:20870:20870 \
  -v tracedown-bodies:/data/bodies \
  -e DATABASE_URL=jdbc:postgresql://your-postgres:5432/tracedown \
  -e DATABASE_USER=tracedown -e DATABASE_PASSWORD= \
  -e REDIS_A_URL=redis://your-redis:6379 \
  -v ./tracedown-monolith-<version>-all.jar:/app.jar:ro \
  eclipse-temurin:21-jre java -jar /app.jar

Behind a reverse proxy (TLS)

Put your web server in front exactly as with the per-service deployment: proxy / to the gateway port and /ws (as a WebSocket upgrade) to the realtime port, then terminate TLS there. One monolith-specific step: set WS_URL=/ws so the dashboard connects to the WebSocket through your proxy on the page's own origin, rather than dialing the realtime port directly — a direct wss://host:20870 has no TLS to speak to. The nginx.conf / apache.conf shipped in the backend's docker/deploy/ directory need only one adjustment: point the frontend locations at the gateway port instead of a static bundle, since the monolith serves the dashboard itself.

The command-line tools

The gateway's CLI tools ride along in the jar, so the one artifact also administers itself:

java -jar tracedown-monolith-<version>-all.jar --create-org "My Org" --owner [email protected]

The owner must be an existing user — --create-org assigns an organization, it does not create an account. Without --owner it falls back to DEMO_USER_EMAIL and fails if no such user exists.

--agent-bootstrap and --agent-remove are present too, for symmetry with the gateway binary, but enrolled agents are never selected in the monolith — probes always run in-process. --rewrap-org-keys is there as well, for moving the org data-encryption keys onto a new PLATFORM_AES_KEY — see Secrets & Encryption.

What "in-process execution" means for results

Results are identical in shape to agent-executed probes — same timing breakdown, same assertion detail, same body handling — with one visible difference: the runs are not attributed to any agent, and probe history shows them without an agent name. Timings are measured from wherever the monolith runs, so latency numbers reflect that machine's network position — the multi-region view that a fleet of agents gives you is precisely the thing this edition trades away.