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

# Reporter deployment

> Deploy Reporter: one binary with a run-mode selector, MongoDB, the report command queue, S3-compatible object storage, Redis or Valkey, worker sizing, and report retention through a bucket lifecycle policy.

Reporter ships as a single binary with two surfaces. `RUN_MODE` selects which surfaces a process serves, so the same image runs as the API, as the report worker, or as both. Four dependencies sit behind them.

Reporter reads your databases through an extraction engine that runs inside the worker process. There is no separate extraction service to deploy.

## What you deploy

***

| Component           | Role                                                                                     | Scale on                       |
| ------------------- | ---------------------------------------------------------------------------------------- | ------------------------------ |
| **API surface**     | REST API for templates, reports, deadlines, and data sources. Publishes report commands. | Request rate.                  |
| **Report worker**   | Queue consumer. Extracts the data, renders the report, and writes the artifact.          | Queue depth.                   |
| **MongoDB**         | Templates, reports, deadlines, and the durable event outbox.                             | Metadata volume.               |
| **RabbitMQ**        | The report command queue, and the business event exchange.                               | Report rate.                   |
| **Object storage**  | Template sources and rendered reports. S3-compatible.                                    | Artifact volume and retention. |
| **Redis or Valkey** | Idempotency locks, schema cache, and tenant lifecycle.                                   | Both surfaces.                 |

## Run modes

***

`RUN_MODE=api` serves every REST operation, plus `/health`, `/readyz`, and `/version`, on the address in `SERVER_ADDRESS`. `RUN_MODE=worker` consumes the report command queue and serves `/health` and `/readyz` on `HEALTH_PORT`. `RUN_MODE=all` runs both surfaces in one process.

Use `all` for local development. In production, deploy the two surfaces separately, so report generation scales on its own.

```bash theme={null}
# API deployment
RUN_MODE=api
SERVER_ADDRESS=:4005

# Worker deployment
RUN_MODE=worker
HEALTH_PORT=4006
```

## MongoDB

***

Both surfaces use the same MongoDB deployment. The API surface writes templates, reports, and deadlines; the worker updates a report as it completes.

In single-tenant mode, `MONGO_HOST` and `MONGO_NAME` are required at startup. In multi-tenant mode, each tenant gets its own database, resolved from the JWT on the request. That path fails closed: a request that carries a tenant with no tenant database returns an error instead of touching a shared database.

## RabbitMQ

***

Two separate concerns share one broker.

### The report command queue

This queue carries work from the API surface to the worker. `RABBITMQ_EXCHANGE`, `RABBITMQ_GENERATE_REPORT_QUEUE`, and `RABBITMQ_GENERATE_REPORT_KEY` name the exchange, the queue, and the routing key. The API surface publishes, so it needs all three. The worker only consumes, so it needs `RABBITMQ_GENERATE_REPORT_QUEUE` alone. Both surfaces need the broker connection itself, and the objects must exist before the services start.

The channel is internal to Reporter. To learn that a report finished, subscribe to the business events below, or poll the report.

A worker that fails a message retries it up to five times with backoff, and then rejects it without requeue. Bind a dead-letter exchange to the command queue, so a rejected report lands somewhere you can inspect.

### The events exchange

Business events (`template.*`, `report.*`, `deadline.*`) go to an exchange you name in `RABBITMQ_REPORT_EVENTS_EXCHANGE`. The value is operator-configured; `reporter.events` is the reference value.

Set that exchange, `STREAMING_BROKERS`, and `STREAMING_CLOUDEVENTS_SOURCE` whenever `STREAMING_ENABLED=true`. With streaming off, both surfaces start normally and publish nothing.

## Object storage

***

Reporter uses one S3-compatible bucket, named in `OBJECT_STORAGE_BUCKET`. It holds two kinds of object, each under its own prefix:

* the template source, as `templates/<templateId>.tpl`
* the rendered report, as `reports/<templateId>/<reportId>.<format>`

In multi-tenant mode, both prefixes sit under the tenant that owns the object: `<tenantId>/templates/...` and `<tenantId>/reports/...`.

AWS S3, MinIO, and SeaweedFS all work. Reach SeaweedFS through its S3 gateway. `OBJECT_STORAGE_USE_PATH_STYLE=true` is what MinIO and SeaweedFS expect, and `OBJECT_STORAGE_DISABLE_SSL` stays `false` outside local development.

### Report retention

Reporter keeps every report it renders, so the bucket grows with your report volume. Set expiry on the bucket, with the object store's own lifecycle policy, over the window your retention rules require.

<Warning>
  **Anchor the rule on the report prefix that your tenancy mode produces.** In single-tenant mode every report key starts at `reports/`, so one rule on that prefix covers the bucket. In multi-tenant mode the key carries the tenant ahead of it, so the rule needs the full `<tenantId>/reports/` prefix, one rule per tenant. Keep `templates/` out of scope either way: a rule that covers the whole bucket also removes the templates your reports are rendered from.
</Warning>

## Redis or Valkey

***

`REDIS_HOST` is required on the API surface. On the worker it is required only when `MULTI_TENANT_ENABLED=true`, where it caches tenant discovery. Redis backs the idempotency lock on report creation, the datasource schema cache, and tenant lifecycle messages in multi-tenant mode.

Idempotency state lives in Redis, not in process memory, so API replicas share it. The same report request sent twice, to two replicas, creates one report.

## Data sources

***

Reporter reads reporting data from PostgreSQL and MongoDB data sources declared in the environment, one `DATASOURCE_{NAME}_*` block per source. See [Environment variables](/en/reporter/reporter-environment-variables) for the block.

There is no API that registers a data source, so a new source is a configuration change and a restart. Reporter also starts with none configured, and serves templates, deadlines, and metrics.

## Sizing the worker

***

`RABBITMQ_NUMBERS_OF_WORKERS` sets how many report jobs one worker process runs in parallel. For horizontal scale, add worker replicas and drive them from the depth of the command queue.

PDF output renders through a headless-browser pool: `PDF_POOL_WORKERS` concurrent renders, which defaults to 2, each bounded by `PDF_TIMEOUT_SECONDS`, which defaults to 90. Size worker memory against that pool, not only against the rows a report reads. The other output formats do not use it.

Every report also carries fixed extraction limits. They are 10 data sources, 50 tables per data source, and 200 fields per table, with 4 data sources read at a time, a 300-second deadline, and a 100 MiB cap on extracted data. The `ENGINE_*` variables change them.

## Deployment mode and TLS

***

`DEPLOYMENT_MODE` declares the flavor of the deployment: `local`, `byoc`, or `saas`. It tags the `/readyz` response, and in `saas` it enforces TLS.

<Warning>
  **SaaS mode requires TLS on every dependency.** Set `DEPLOYMENT_MODE=saas` and a plaintext MongoDB, RabbitMQ, Redis, object storage, or Tenant Manager URL stops the process. It stops before any connection opens.
</Warning>

Leave `ALLOW_INSECURE_TLS` unset in production. It bypasses those checks, and the local development stack is the only place for it.

## Startup checks

***

Reporter validates its configuration before it serves anything. Each check below stops the process, and the error names every variable at fault.

| Trigger                                                                                                                                                                            | What the operator sees                                         |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SERVER_ADDRESS`, `REDIS_HOST`, or any of the three command-queue variables missing on the API surface                                                                             | Config validation fails and lists each missing variable.       |
| `RABBITMQ_GENERATE_REPORT_QUEUE` missing on the worker surface                                                                                                                     | Config validation fails and names it.                          |
| `MONGO_HOST` or `MONGO_NAME` missing, single-tenant mode                                                                                                                           | Config validation names them.                                  |
| `MULTI_TENANT_ENABLED=true` without `MULTI_TENANT_URL` or `MULTI_TENANT_SERVICE_API_KEY`                                                                                           | Config validation names the variable the tenant runtime needs. |
| `STREAMING_ENABLED=true` without `RABBITMQ_REPORT_EVENTS_EXCHANGE`, `STREAMING_BROKERS`, and `STREAMING_CLOUDEVENTS_SOURCE` — all three are required for a streaming-enabled start | Startup aborts rather than run with events that go nowhere.    |
| `DEPLOYMENT_MODE=saas` with a plaintext dependency URL                                                                                                                             | Startup aborts before any connection opens.                    |

<Note>
  **A dependency that is down at boot does not produce a silently broken pod.** `/health` answers 503 until the startup self-probe succeeds. The kubelet then restarts the pod instead of sending it traffic.
</Note>

## Rolling updates

***

On `SIGTERM`, both surfaces enter a drain. `/readyz` answers 503 from the moment the signal arrives, before the servers begin shutdown, and in-flight requests and in-flight messages finish. Kubernetes removes the pod from Service endpoints while it still works.

Set the termination grace period above your longest report render. See the [health and readiness reference](/en/reference/health-and-readiness) for what the probes report during a drain.

## Next steps

***

<CardGroup cols={2}>
  <Card title="Environment variables" icon="gear" href="/en/reporter/reporter-environment-variables">
    Every Reporter variable, by category.
  </Card>

  <Card title="BYOC configuration" icon="sliders" href="/en/reference/byoc-configuration">
    The configuration blocks every Lerian product shares.
  </Card>

  <Card title="Health and readiness" icon="heart-pulse" href="/en/reference/health-and-readiness">
    The probe contract and what each response means.
  </Card>

  <Card title="Connect Reporter to Midaz" icon="link" href="/en/reporter/connecting-reporter-to-midaz">
    Point Reporter at a Midaz database and render your first report.
  </Card>
</CardGroup>
