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 (20810–20870) 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.