> For the complete documentation index, see [llms.txt](https://cantonindexer.infradao.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cantonindexer.infradao.com/install.md).

# Install

You need a PQS to read from, `cinder` on your machine, and, to index continuously, the `canton-indexer` node with a Postgres database of its own.

## Prerequisites: a PQS in tree mode

Canton Indexer reads your validator's PQS (Participant Query Store) over a `postgres://` URL. You need network access to that database and a user that can read it.

PQS must ingest transaction trees. Set its datasource to `TransactionTreeStream`: the command-line option is `--pipeline-datasource=TransactionTreeStream`, the configuration key is `pipeline.datasource`. The default, `TransactionStream`, stores no exercises, so every filter with `source = "exercises"` finds nothing. Switching an existing PQS from one mode to the other is not supported by Digital Asset; they expect you to reseed the PQS database.

Canton Indexer has been run against PQS 3.5.8.

## Install cinder

`cinder` is published for these platforms. There is no Windows build.

| Platform             | Release asset           |
| -------------------- | ----------------------- |
| macOS, Apple silicon | `cinder-aarch64-darwin` |
| macOS, Intel         | `cinder-x86_64-darwin`  |
| Linux, x86\_64       | `cinder-x86_64-linux`   |
| Linux, ARM64         | `cinder-aarch64-linux`  |

The Linux binaries are statically linked (musl), so they need no system libraries.

### Homebrew (macOS and Linux)

```sh
brew install infradao/tap/cinder
cinder --version
```

Upgrade with `brew upgrade cinder`.

### Manual download

Download your asset and the `SHA256SUMS` file from the [releases page](https://github.com/InfraDAO/canton-indexer-releases/releases), check the hash, then put the binary on your `PATH`:

```sh
VERSION=v0.10.0                # the release you want
ASSET=cinder-aarch64-darwin    # your platform, from the table above
BASE=https://github.com/InfraDAO/canton-indexer-releases/releases/download/$VERSION
curl -fsSLO "$BASE/$ASSET"
curl -fsSLO "$BASE/SHA256SUMS"

# macOS
grep " $ASSET\$" SHA256SUMS | shasum -a 256 -c -
# Linux
grep " $ASSET\$" SHA256SUMS | sha256sum -c -

chmod +x "$ASSET"
mv "$ASSET" /usr/local/bin/cinder
```

The check prints `cinder-aarch64-darwin: OK` when the hash matches.

The macOS binaries are not notarized by Apple. If macOS refuses to open `cinder`, see [Troubleshooting](/troubleshooting.md#macos-blocks-cinder).

## Configure cinder

`cinder` reads one TOML file. It uses the first of:

1. the file you pass with `--config PATH`
2. `./cinder.toml` in the current directory
3. `$XDG_CONFIG_HOME/cinder/config.toml`, or `~/.config/cinder/config.toml` when `XDG_CONFIG_HOME` is unset or empty

```toml
pqs_url = "postgres://user:password@pqs-host:5432/pqs"
node = "https://indexer.example.com:8020"
```

| Key       | Used by                   | Value                                                                                 |
| --------- | ------------------------- | ------------------------------------------------------------------------------------- |
| `pqs_url` | `types`, `preview`, `run` | The PQS database URL.                                                                 |
| `node`    | `deploy`, `ls`, `info`    | The node's management address. `http://` connects in plain text, `https://` over TLS. |

Any other key makes `cinder` stop with an error. The file is the only place `pqs_url` and `node` come from. `cinder` reads `RUST_LOG` for the log level (`RUST_LOG=debug`), and `XDG_CONFIG_HOME` and `HOME` to find the file. What `pqs_url` leaves out, such as a password or `sslmode`, is taken from the libpq environment variables (`PGPASSWORD`, `PGSSLMODE`, `PGSSLROOTCERT`, `PGOPTIONS` and others) when they are set; see [TLS to PQS](#tls-to-pqs).

Over `https://`, `cinder` checks the node's certificate against the Mozilla CA bundle built into it. It has no option to trust another CA.

### TLS to PQS

`pqs_url` takes the usual Postgres URL parameters, read by the [sqlx](https://docs.rs/sqlx/0.9.0/sqlx/postgres/struct.PgConnectOptions.html) library. sqlx starts from the libpq environment variables (`PGHOST`, `PGPASSWORD`, `PGSSLMODE`, `PGSSLROOTCERT` and others), and a parameter in the URL overrides its variable; the URL's `options` are added to `PGOPTIONS`. With neither `sslmode` in the URL nor `PGSSLMODE` set, the connection uses `prefer`: TLS when the server offers it, without checking the certificate.

| You want                                    | Add to `pqs_url`                                   |
| ------------------------------------------- | -------------------------------------------------- |
| TLS, certificate not checked                | `?sslmode=require`                                 |
| TLS, certificate checked against public CAs | `?sslmode=verify-full`                             |
| TLS, certificate from your own CA           | `?sslmode=verify-full&sslrootcert=/path/to/ca.crt` |

Public CAs here are the Mozilla CA bundle built into `cinder`.

## Run the node

The node is the container image `ghcr.io/infradao/canton-indexer-releases/canton-indexer`, published from v0.10.0 on, built for `linux/amd64` and `linux/arm64`. Each release is tagged with its version, such as `v0.10.0`; there is no `latest` tag. The image runs the static `canton-indexer` binary on Alpine, as user id 65532.

The node needs:

* a PQS, the same one `cinder` reads (`PQS_URL`);
* its own Postgres database, the **store**. The database must exist. On start the node applies its migrations and creates the schema `indexer`, and it creates one schema `target<id>_v<version>` per deployed filter version, so its user needs the right to create schemas there. The migrations also create the table `public._sqlx_migrations`. From Postgres 15 on, a user that does not own the database cannot create in schema `public` by default, so grant it:

  ```sql
  GRANT CREATE ON DATABASE indexer TO indexer;
  GRANT CREATE ON SCHEMA public TO indexer;
  ```

```sh
TAG=v0.10.0
docker run --rm \
  -e PQS_URL='postgres://user:password@pqs-host:5432/pqs' \
  -e DB_HOST=store-host -e DB_USER=indexer -e DB_PASSWORD=secret -e DB_NAME=indexer \
  -e NO_AUTH_GRPC_ADDR=0.0.0.0:8020 \
  -p 127.0.0.1:8020:8020 -p 9464:9464 \
  ghcr.io/infradao/canton-indexer-releases/canton-indexer:$TAG
```

The node reads its settings from the environment. Each one also has a flag, such as `--db-host`.

| Variable                    | Default                | What it sets                                                                                                                                          |
| --------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PQS_URL`                   | required               | The PQS database URL.                                                                                                                                 |
| `DB_HOST`                   | `localhost`            | Host of the store.                                                                                                                                    |
| `DB_PORT`                   | `5432`                 | Port of the store.                                                                                                                                    |
| `DB_USER`                   | `indexer`              | User the node connects to the store as.                                                                                                               |
| `DB_PASSWORD`               | `indexer`              | That user's password.                                                                                                                                 |
| `DB_NAME`                   | the value of `DB_USER` | Database of the store.                                                                                                                                |
| `NO_AUTH_GRPC_ADDR`         | `127.0.0.1:8020`       | Where the management service listens. `cinder deploy` connects here. In a container, set `0.0.0.0:8020`: the default is the container's own loopback. |
| `METRICS_ADDR`              | `0.0.0.0:9464`         | Where Prometheus metrics are served, at `/metrics`.                                                                                                   |
| `SHUTDOWN_GRACE_SECS`       | `60`                   | How long a shutdown waits for filter tasks to stop.                                                                                                   |
| `RESPAWN_BACKOFF_SECS`      | `1`                    | Wait before a failed task's first restart. It doubles with each failure in a row.                                                                     |
| `RESPAWN_BACKOFF_CAP_SECS`  | `120`                  | Longest wait before a restart.                                                                                                                        |
| `RESPAWN_STABLE_AFTER_SECS` | `300`                  | How long a task must run before its failure count resets.                                                                                             |
| `IGNORE_NEWER_MIGRATIONS`   | `false`                | `true` boots on a store that a newer node migrated, skipping the migrations this binary does not have. For a rollback.                                |
| `RUST_LOG`                  | `info`                 | Log level. Logs go to stderr.                                                                                                                         |

The four `*_SECS` settings refuse `0`. For TLS to the store, set `PGSSLMODE` and the other `PGSSL*` variables that sqlx reads. They also fill in what `PQS_URL` leaves out, such as `sslmode`. For TLS to PQS, add `sslmode` to `PQS_URL` as in [TLS to PQS](#tls-to-pqs).

**The management service has no authentication.** Anyone who can reach its port can deploy a filter, replace any filter with `--replace`, which deletes that filter's documents, or deploy a filter that ends the node. Publish the port only on localhost or a private network, as `-p 127.0.0.1:8020:8020` does above, never on a public address. `/metrics` on port 9464 has no authentication either.

The management service speaks plain gRPC. To reach it with an `https://` `node` URL, put it behind a proxy or ingress that terminates TLS with a certificate from a public CA. A proxy that only terminates TLS adds no authentication: it must also restrict who can call. `cinder` sends no credentials or client certificate, so the proxy can only restrict callers by network, such as an address allowlist or a VPN.

On start the node applies migrations, connects to PQS, and starts a task for every deployed filter that is `active`. If the store or PQS cannot be reached, it exits with code 1.

On `SIGTERM` or `SIGINT` it stops accepting gRPC connections, waits for open ones to close, then gives filter tasks `SHUTDOWN_GRACE_SECS` to stop. In Kubernetes, set `terminationGracePeriodSeconds` above that grace: the Kubernetes default of 30 seconds is shorter than the node's default of 60.

### Metrics

Per filter: `indexer_filter_running`, `indexer_filter_last_offset`, `indexer_filter_commits_total`, `indexer_filter_documents_total`, `indexer_filter_skipped_total` (by `filter_id` and `version`; records skipped under `on_error = "skip"`), `indexer_filter_source_wait_seconds` (a histogram), `indexer_filter_failures` (failures in a row) and `indexer_filter_respawns_total`. For the node: `indexer_tracker_restarts_total`, and `indexer_filters` by status. Counters start at zero when the node starts.

## Sources

* Release v0.10.0 and its assets, published 2026-10-22 (the node image, from v0.10.0 on, with version tags only, for `linux/amd64` and `linux/arm64`): <https://github.com/InfraDAO/canton-indexer-releases/releases/tag/v0.10.0>
* The node's settings and defaults, including `NO_AUTH_GRPC_ADDR` and `IGNORE_NEWER_MIGRATIONS`: the v0.10.0 release asset `canton-indexer-config.md`, <https://github.com/InfraDAO/canton-indexer-releases/releases/download/v0.10.0/canton-indexer-config.md>
* The image's base and user (Alpine, user id 65532), and the metrics: canton-indexer source at tag v0.10.0, `Dockerfile` and `crates/indexer/src/metrics.rs`
* Release list: <https://github.com/InfraDAO/canton-indexer-releases/releases>
* Homebrew formula: <https://github.com/InfraDAO/homebrew-tap/blob/main/Formula/cinder.rb>
* PQS datasource option (`--pipeline-datasource`, default `TransactionStream`), in the scribe help text of PQS v3.5.8: <https://github.com/digital-asset/participant-query-store/blob/v3.5.8/apps/scribe/functest/com/digitalasset/scribe/console/HelpSpec.scala#L262-L267>
* PQS Helm chart values (`pipeline.datasource`): <https://github.com/digital-asset/participant-query-store/blob/v3.5.8/charts/participant-query-store/README.md>
* Switching PQS modes needs a reseed (Canton Network forum): <https://forum.canton.network/t/switching-pqs-from-transactionstream-to-transactiontreestream-mode/8465>
* sqlx `PgConnectOptions` (URL parameters, `PGSSL*` variables, `sslmode` default): <https://docs.rs/sqlx/0.9.0/sqlx/postgres/struct.PgConnectOptions.html>
* Kubernetes pod termination grace period: <https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination>
