# Container labels reference

> Every lumin.* Docker label the agent reads, what it does, and the defaults.

Canonical: https://docs.getlumin.dev/agent/labels/

The agent inspects Docker labels on every running container every 30 seconds and opts each container into log tailing and metric scraping based on the labels below.

All labels are namespaced under `lumin.`. The agent never *writes* labels — it only reads them.

## Log shipping

### `lumin.logs`

**Required to ship logs.** Setting this to `true` (the string) tells the agent to open a follow-stream against the container's stdout and stderr and forward each line to `/v1/logs`.

```yaml
labels:
  lumin.logs: "true"
```

Without this label, the agent does not tail the container's logs. Container metrics are still collected (those are universal); only log tailing is opt-in.

**Adding this label ships the container's log stream verbatim.** Every line goes to Lumin exactly as the container wrote it, including anything secret it happens to log — a token in a query string, a password in a connection string, a request body in a stack trace. The agent does not redact, by design. Read [Log content is your responsibility](https://docs.getlumin.dev/agent/security/#log-content-is-your-responsibility) before labelling a container whose output you have not looked at.

### `lumin.service`

**Optional.** Overrides the `service` field stamped on each row. Defaults to the container name.

```yaml
labels:
  lumin.logs: "true"
  lumin.service: "api"
```

Use this when the container name is autogenerated (`myapp-api-1`) but you want logs to land under a stable service name (`api`) in the UI.

## Metric scraping

### `lumin.metrics`

**Required to scrape metrics.** Setting this to `true` tells the agent to scrape the container's Prometheus `/metrics` endpoint on every tick.

```yaml
labels:
  lumin.metrics: "true"
  lumin.metrics.port: "8080"
  lumin.metrics.framework: "gin"
```

### `lumin.metrics.port`

**Required when `lumin.metrics=true`.** The container port that exposes `/metrics`. The agent connects over the Compose bridge network — it does not go through any published port mapping.

### `lumin.metrics.path`

**Optional.** Defaults to `/metrics`. Override when your framework exposes Prometheus exposition at a different path (Spring Boot Actuator: `/actuator/prometheus`).

### `lumin.metrics.framework`

**Optional but recommended.** Tells the agent how to map framework-specific metric names onto Lumin's canonical `http.*` schema, so different services chart side by side in the UI.

Supported values: `gin`, `spring`, `express`, `fastapi`, `axum`.

If you don't set this, the agent ingests raw metric names verbatim and the canned Metrics-page panels (KPIs, latency histogram, top routes) will not light up for that service.

See the [stack guides](https://docs.getlumin.dev/stacks/) for the exact framework value to pair with each library.

## Worked example

```yaml
services:
  api:
    image: my-api:latest
    labels:
      lumin.logs: "true"
      lumin.service: "api"
      lumin.metrics: "true"
      lumin.metrics.port: "8080"
      lumin.metrics.framework: "gin"

  postgres:
    image: postgres:16
    labels:
      lumin.logs: "true"
      lumin.service: "postgres"
      # No metrics labels — postgres doesn't expose Prometheus exposition by default,
      # and we'd rather not run an exporter sidecar just for this.

  caddy:
    image: caddy:2
    labels:
      lumin.logs: "true"
      lumin.service: "caddy"
```

## What happens when labels change

Label changes are picked up on the next discovery tick (every 30 seconds). Adding `lumin.logs=true` opens a new tail; removing it closes the existing tail at the next tick. Recreating a container with new labels (`docker compose up -d service`) effectively forces immediate rediscovery — the old container ID disappears, the new one is picked up.
