> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vexlo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Install and run the server

> Build the vexlo binary from a checkout, start the Vexlo server, and check its configuration and retention arithmetic before it takes traffic.

The Vexlo server is one Go binary, `vexlo`. It ingests records from relay daemons, owns storage,
and serves the `/v1` API and the UI from one address. This page builds it, starts it, and
explains every flag `vexlo serve` and `vexlo check` take. Tokens are on
[Authentication and tokens](https://docs.vexlo.ai/server/authentication.md); the daemon that feeds the server is on
[The relay daemon](https://docs.vexlo.ai/server/relay-daemon.md).

Flags are spelled with a single dash throughout, as the binary's own `-h` output prints them.
The binary accepts the double-dash spelling too.

## Requirements

The server repository is not yet public, so the build starts from a checkout rather than a
download. It needs:

* Go 1.27
* Node.js 22.12 or later, for the UI that the binary embeds
* A C toolchain. The SQLite driver is cgo, and the Makefile exports
  `CGO_ENABLED=1` for every target that needs it.

## Build

```bash theme={null}
make setup   # npm ci for web/, go mod download, gofumpt and golangci-lint, git hooks
make build   # builds web/ first, then go build -o bin/vexlo ./cmd/vexlo
```

`make build` has to run before a bare `go build ./...` works: the server embeds the built UI with
`//go:embed`, so `web/dist` must exist before the Go build that embeds it can compile.

Running the binary with no arguments lists its commands and exits 1:

```text theme={null}
$ bin/vexlo
vexlo: usage: vexlo <command> [args]
  commands: serve, check, relay, token, policy
```

Every command below is written `vexlo`. From a checkout that is `bin/vexlo`, unless `bin/` is
on your `PATH`.

## The six commands

| Command | What it does | Page |
| - | - | - |
| `vexlo serve` | The API and the embedded UI. Binds `127.0.0.1:8080` by default. | this page |
| `vexlo check` | Validates the configuration `serve` would use, without opening storage. | this page |
| `vexlo relay run` | The relay daemon: tails a spool directory and forwards it to a server. | [The relay daemon](https://docs.vexlo.ai/server/relay-daemon.md) |
| `vexlo relay status` | One-shot summary of a spool directory's state. | [The relay daemon](https://docs.vexlo.ai/server/relay-daemon.md) |
| `vexlo token` | Creates, lists, and revokes API tokens. | [Authentication and tokens](https://docs.vexlo.ai/server/authentication.md) |
| `vexlo policy` | Reads and updates a service's capture policy on a running server. | [Capture policy](https://docs.vexlo.ai/server/capture-policy.md) |

Each leaf command prints its own flags: `vexlo serve -h`, `vexlo check -h`,
`vexlo relay run -h`, `vexlo relay status -h`, `vexlo token create -h` (and `list`,
`revoke`), `vexlo policy set -h` (and `show`). Each exits 1 after printing, and its last line
reads `vexlo: flag: help requested`. `vexlo token -h`, `vexlo policy -h`,
`vexlo relay -h`, and `vexlo -h` print no flags: each reads `-h` as a command name it does
not recognize, prints the names it does accept, and exits 1.

## Run the server

```bash theme={null}
vexlo serve
```

With no flags the server binds `127.0.0.1:8080`, applies its database migrations on startup (a
fresh, empty database file needs no separate migration step), and stores everything under
`~/.vexlo/data`: the SQLite file `vexlo.db` and the value store directory `values/`. That
directory is the server storage directory. `vexlo serve` resolves it from `-db-path` and
`-value-store-dir`, each falling back to its own environment variable, then to
`VEXLO_DATA_DIR`, then to `~/.vexlo/data`. It never reads the `-data-dir` flag `vexlo token`
takes, so a token minted under an explicit `-data-dir` is found on the first start only when
the server names the same directory.

The first log line records the eight settings it names and no others: the bind address,
whether TLS is required, `-no-auth` and `-header-trust`, the two storage paths, and the two
retention settings. `-team`, `-tls-cert`, `-tls-key`, and `-allow-insecure-http` never appear in it.
The request log lines that follow are omitted here. The run below bound a non-default port
and named a scratch server storage directory with `-db-path` and `-value-store-dir`:

```text theme={null}
$ vexlo serve -api-addr 127.0.0.1:18100 -db-path data/vexlo.db -value-store-dir data/values
time=2026-09-14T01:49:17.812-07:00 level=INFO msg="serve: starting" addr=127.0.0.1:18100 requires_tls=false no_auth=false header_trust=false db_path=data/vexlo.db value_store_dir=data/values shape_retention_days=7 value_budget_bytes=21474836480
... 30 lines omitted
```

Every request is logged with its method, path, status, duration, and request id; the
`Authorization` header is never logged. Ctrl-C (SIGINT) or SIGTERM stops the server: new
requests are refused and in-flight ones get 30 seconds to finish. On a clean drain the last
line is `msg="serve: shutting down"` and the exit code is 0. A request still running after
those 30 seconds is abandoned, and the process exits 1 with an error on its last line
instead.

## Loopback or TLS

The server refuses to bind a non-loopback address without a TLS certificate. Loopback means a
host of `127.0.0.1`, `::1`, or `localhost`, matched as a string once it is lowercased and a
trailing dot is dropped, never resolved through DNS. `vexlo check` applies the same rule as
`vexlo serve` and reports it without opening a port:

```text theme={null}
$ vexlo check -api-addr 0.0.0.0:18100 -db-path data/vexlo.db -value-store-dir data/values
vexlo: check: vexlo serve: refusing to bind 0.0.0.0:18100: not loopback and no TLS certificate configured; set -tls-cert/-tls-key (or VEXLO_TLS_CERT_FILE/VEXLO_TLS_KEY_FILE), bind to loopback (127.0.0.1, ::1, localhost) for local dev, or set -allow-insecure-http/VEXLO_ALLOW_INSECURE_HTTP
```

The refusal names the three ways forward.

> [tab] Terminate TLS in the binary
>
> Pass `-tls-cert` and `-tls-key` (or `VEXLO_TLS_CERT_FILE` and `VEXLO_TLS_KEY_FILE`). Both
> files are loaded at startup, so a missing or malformed pair fails `vexlo check` and
> `vexlo serve` alike rather than a first request. A supplied certificate is always used, even
> on a loopback bind. The certificate below is self-signed, on a loopback address:
>
> ```text theme={null}
> $ vexlo serve -api-addr 127.0.0.1:18103 -tls-cert tls/cert.pem -tls-key tls/key.pem \
>     -db-path data-tls/vexlo.db -value-store-dir data-tls/values
> time=2026-09-14T01:54:08.961-07:00 level=INFO msg="serve: starting" addr=127.0.0.1:18103 requires_tls=true no_auth=false header_trust=false db_path=data-tls/vexlo.db value_store_dir=data-tls/values shape_retention_days=7 value_budget_bytes=21474836480
> ... 3 lines omitted
> ```
>
> ```text theme={null}
> $ curl -sk -i https://127.0.0.1:18103/v1/capabilities
> HTTP/2 200 
> content-type: application/json
> x-request-id: 8013cf722a137abb
> ... 4 lines omitted
> ```
>
> A plain `http://` request to that port is answered
> `400 Bad Request` with the body `Client sent an HTTP request to an HTTPS server.`

> [tab] Terminate TLS in a reverse proxy
>
> Run a reverse proxy that terminates TLS and forwards to the server on loopback. The server
> sees a loopback bind and asks for nothing further. Vexlo does not issue or rotate
> certificates itself.

> [tab] Accept plaintext
>
> `-allow-insecure-http` (or `VEXLO_ALLOW_INSECURE_HTTP=1`) accepts a non-loopback bind with no
> certificate. A warning is logged on every startup while it is set:
>
> ```text theme={null}
> $ vexlo check -api-addr 0.0.0.0:18100 -allow-insecure-http -allow-overcommit \
>     -db-path data/vexlo.db -value-store-dir data/values
> time=2026-09-14T01:48:52.139-07:00 level=WARN msg="vexlo serve: -allow-insecure-http/VEXLO_ALLOW_INSECURE_HTTP is set — accepting a non-loopback bind with no TLS certificate configured" addr=0.0.0.0:18100
> config: OK
> ... 16 lines omitted
> ```

## Check the configuration

`vexlo check` resolves the identical configuration `vexlo serve` would run with, validates it,
reports free disk on the value store's filesystem, and prints the retention arithmetic. It never
opens the database or the value store. Run it with the same flags and environment you will start
the server with.

On the default retention settings it exits 1. A 20 GiB value budget at the default expected
write rate of 2 traces per second holds about 104 hours of values, and the default shape
retention is 168 hours, so every trace would lose its values before it ages out of search. The
run below names a scratch server storage directory; the defaults are
`~/.vexlo/data/vexlo.db` and `~/.vexlo/data/values`:

```text theme={null}
$ vexlo check -db-path data/vexlo.db -value-store-dir data/values
config: OK
  api_addr:             127.0.0.1:8080 (requires_tls=false)
  no_auth:              false
  header_trust:         false
  db_path:              data/vexlo.db
  value_store_dir:      data/values
  shape_retention_days: 7
  value_budget_bytes:   21474836480
free disk (value_store_dir): 107840569344 bytes free, 21474836480 bytes configured budget
retention arithmetic (at 2 traces/s):
  shape bytes/trace:          2300
  value bytes/trace:          28700
  total bytes/trace:          31000
  configured shape retention: 168h0m0s
  effective value retention:  103h55m26.071080139s
  OVERCOMMITTED: values would run out (103h55m26.071080139s) before shape retention expires (168h0m0s) — every trace would lose its values well before it ages out of search
vexlo: check: value store is overcommitted against the configured shape retention (-shape-retention-days/-value-budget-bytes/-expected-write-rate); lower -shape-retention-days, raise -value-budget-bytes, or pass -allow-overcommit to proceed anyway
```

For a first run on one machine, `-allow-overcommit` is the lever to pick. The check then
exits 0 and prints the same arithmetic with one extra line:

```text theme={null}
$ vexlo check -db-path data/vexlo.db -value-store-dir data/values -allow-overcommit
... 8 lines omitted
free disk (value_store_dir): 107840565248 bytes free, 21474836480 bytes configured budget
retention arithmetic (at 2 traces/s):
  shape bytes/trace:          2300
  value bytes/trace:          28700
  total bytes/trace:          31000
  configured shape retention: 168h0m0s
  effective value retention:  103h55m26.071080139s
  OVERCOMMITTED: values would run out (103h55m26.071080139s) before shape retention expires (168h0m0s) — every trace would lose its values well before it ages out of search
  proceeding: -allow-overcommit is set
```

`vexlo serve` has no `-allow-overcommit` flag and applies no overcommit check of its own: it
starts on the configuration `vexlo check` rejects. The check is where the arithmetic is read.

`-expected-write-rate` changes the figure the arithmetic is computed at. `vexlo check` has no
live traffic to measure it from, so a production estimate has to be passed in.

## Flag reference

Both commands share one flag set; `check` adds two of its own. The `serve -h` output as printed:

```text theme={null}
$ vexlo serve -h
Usage of serve:
  -allow-insecure-http
    	accept a non-loopback bind with no TLS certificate configured (env VEXLO_ALLOW_INSECURE_HTTP)
  -api-addr string
    	read API bind address, host:port (env VEXLO_API_ADDR), default 127.0.0.1:8080
  -db-path vexlo token
    	SQLite database file (env VEXLO_DB_PATH, default alongside vexlo token's own storage)
  -header-trust
    	trust X-Vexlo-Team on the read API when Authorization is absent (env VEXLO_HEADER_TRUST)
  -header-trust-secret string
    	shared secret a reverse proxy injects as X-Vexlo-Trust-Secret; required with -header-trust (env VEXLO_HEADER_TRUST_SECRET)
  -no-auth
    	disable authentication entirely; forces a loopback bind (env VEXLO_NO_AUTH)
  -shape-retention-days int
    	shape (trace/step/attempt) retention, in days (default 7)
  -team string
    	team every ingested row and query is scoped to under -no-auth (env VEXLO_TEAM), default "default"
  -tls-cert string
    	TLS certificate file; required unless -api-addr is loopback (env VEXLO_TLS_CERT_FILE)
  -tls-key string
    	TLS private key file; required unless -api-addr is loopback (env VEXLO_TLS_KEY_FILE)
  -value-budget-bytes int
    	value store byte budget, in bytes (default 20 GiB) (default 21474836480)
  -value-store-dir vexlo token
    	content-addressed value store directory (env VEXLO_VALUE_STORE_DIR, default alongside vexlo token's own storage)
vexlo: flag: help requested
```

> [!NOTE]
>
> `-db-path` and `-value-store-dir` render their placeholder as `vexlo token`; both take a path.
> The phrase is the help text's reference to `vexlo token`'s own storage, picked up as the
> value name.

`vexlo check -h` prints the same flags plus `-allow-overcommit` and `-expected-write-rate`:

> [accordion] vexlo check -h
>
> ```text theme={null}
> $ vexlo check -h
> Usage of check:
>   -allow-insecure-http
>     	accept a non-loopback bind with no TLS certificate configured (env VEXLO_ALLOW_INSECURE_HTTP)
>   -allow-overcommit
>     	proceed (exit 0) even when the configured value budget would run dry before shape retention expires
>   -api-addr string
>     	read API bind address, host:port (env VEXLO_API_ADDR), default 127.0.0.1:8080
>   -db-path vexlo token
>     	SQLite database file (env VEXLO_DB_PATH, default alongside vexlo token's own storage)
>   -expected-write-rate float
>     	expected sustained write rate, in traces/s, used to compute effective value retention (vexlo check has no live traffic to measure this from) (default 2)
>   -header-trust
>     	trust X-Vexlo-Team on the read API when Authorization is absent (env VEXLO_HEADER_TRUST)
>   -header-trust-secret string
>     	shared secret a reverse proxy injects as X-Vexlo-Trust-Secret; required with -header-trust (env VEXLO_HEADER_TRUST_SECRET)
>   -no-auth
>     	disable authentication entirely; forces a loopback bind (env VEXLO_NO_AUTH)
>   -shape-retention-days int
>     	shape (trace/step/attempt) retention, in days (default 7)
>   -team string
>     	team every ingested row and query is scoped to under -no-auth (env VEXLO_TEAM), default "default"
>   -tls-cert string
>     	TLS certificate file; required unless -api-addr is loopback (env VEXLO_TLS_CERT_FILE)
>   -tls-key string
>     	TLS private key file; required unless -api-addr is loopback (env VEXLO_TLS_KEY_FILE)
>   -value-budget-bytes int
>     	value store byte budget, in bytes (default 20 GiB) (default 21474836480)
>   -value-store-dir vexlo token
>     	content-addressed value store directory (env VEXLO_VALUE_STORE_DIR, default alongside vexlo token's own storage)
> vexlo: flag: help requested
> ```

Four flags have no environment-variable form: `-shape-retention-days`, `-value-budget-bytes`,
`-allow-overcommit`, and `-expected-write-rate`. The rest resolve flag first, then environment
variable, then default; `-db-path` and `-value-store-dir` take one step more,
`VEXLO_DATA_DIR`, before that default. The three boolean environment variables
(`VEXLO_ALLOW_INSECURE_HTTP`, `VEXLO_NO_AUTH`, `VEXLO_HEADER_TRUST`) count as set for any
value other than empty, `0`, or `false`, case-insensitive.

| Flag | Environment variable | Default |
| - | - | - |
| `-api-addr` | `VEXLO_API_ADDR` | `127.0.0.1:8080` |
| `-tls-cert` | `VEXLO_TLS_CERT_FILE` | none |
| `-tls-key` | `VEXLO_TLS_KEY_FILE` | none |
| `-allow-insecure-http` | `VEXLO_ALLOW_INSECURE_HTTP` | `false` |
| `-no-auth` | `VEXLO_NO_AUTH` | `false` |
| `-team` | `VEXLO_TEAM` | `default` (used only under `-no-auth`) |
| `-header-trust` | `VEXLO_HEADER_TRUST` | `false` |
| `-header-trust-secret` | `VEXLO_HEADER_TRUST_SECRET` | none; required with `-header-trust` |
| `-db-path` | `VEXLO_DB_PATH` | `$VEXLO_DATA_DIR/vexlo.db`, else `~/.vexlo/data/vexlo.db` |
| `-value-store-dir` | `VEXLO_VALUE_STORE_DIR` | `$VEXLO_DATA_DIR/values`, else `~/.vexlo/data/values` |
| `-shape-retention-days` | none | `7` |
| `-value-budget-bytes` | none | `21474836480` (20 GiB) |
| `-allow-overcommit` (`check` only) | none | `false` |
| `-expected-write-rate` (`check` only) | none | `2` traces/s |

`-no-auth`, `-team`, `-header-trust`, and `-header-trust-secret` are explained on
[Authentication and tokens](https://docs.vexlo.ai/server/authentication.md).

## Retention

Shape (traces, steps, attempts, tags) is kept by day count, `-shape-retention-days`, default 7.
Values are kept against the byte budget, `-value-budget-bytes`, default 20 GiB: when the budget
is full the oldest values go first, and the trace keeps its shape. The function index (observed
callers and callees per function) is kept for 90 days and has no flag. `vexlo check`'s
"effective value retention" line is how long values last at the expected write rate, which is
the number to compare against the shape retention.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.