Attestra

Explorer deployment

Bringing up the Explorer's own infrastructure and the ownership rule that governs its two databases.

This page covers the Explorer's own infrastructure: the database and search index it needs, the order to bring them up in, and a warning worth reading before the first docker compose up. It does not cover the oracle; the Explorer reaches the oracle as a black box, over HTTP, and adds nothing to it.

Topology

The Explorer is one Next.js application, backed by a Postgres database and an Elasticsearch index, calling the oracle over HTTP. Four pieces, three of which the Explorer owns:

  • Next.js application: the pages, API routes, and the process that talks to the other three.
  • Postgres: the Explorer's own database, source of truth for everything it records.
  • Elasticsearch: a search index over the same records, owned by the Explorer, rebuildable from Postgres.
  • The oracle: reached at ORACLE_URL, treated as a black box. The Explorer does not modify it, extend it, or add state to it; it only calls its HTTP endpoints and records what came back.

Environment variables

Three variables configure the Explorer, all read from .env.local:

  • ORACLE_URL: the oracle's HTTP address, for example http://127.0.0.1:8080. The Explorer calls this and nothing else to reach the network.
  • DATABASE_URL: the Postgres connection string, for example postgres://explorer:explorer@127.0.0.1:5432/explorer.
  • ELASTICSEARCH_URL: the Elasticsearch address, for example http://127.0.0.1:9200.

Bring-up order

Three steps, run in this order. Each corresponds to a script in the Explorer's package.json.

  1. Start the infrastructure.

    pnpm infra

    Runs docker compose up -d. Before running this, read the warning below; it concerns exactly this step.

  2. Run the database migration.

    pnpm db:migrate

    Applies every .sql file under migrations/, in sorted order, against DATABASE_URL. This has to succeed before the application can write a single record.

  3. Initialise the search index.

    pnpm es:init

    Creates the Elasticsearch index with its mapping if it does not already exist, and does nothing if it does. Safe to run more than once.

With the infrastructure up, the schema applied, and the index created, the application itself starts the same way any Next.js app does: pnpm dev for local development, or pnpm build followed by pnpm start for a production run.

Ownership: which database is truth

This is the one rule an operator must not get wrong.

Postgres is the source of truth. Elasticsearch is a rebuildable view of it, not a second source. Every write goes to Postgres first; only after a record is committed there does the Explorer index it into Elasticsearch, and that indexing step is best effort. A failed or delayed index does not lose a visitor's answer, because the record it describes already exists in Postgres.

The consequence follows directly: if the Elasticsearch index drifts from Postgres, or is lost outright, it is rebuilt from Postgres. The reverse never happens. Nothing is recovered from Elasticsearch into Postgres, and no visitor-facing data is allowed to live only in the index. If a page depends on data that exists solely in Elasticsearch, that is a defect to fix, not a tradeoff to document.

Warning: check the port before starting the infrastructure

The Explorer's docker-compose.yml defines Elasticsearch only, and it is worth knowing why.

A Postgres service used to be defined there as well. It bound port 5432, the same port a native Postgres instance on the same machine was already using. Bringing the compose stack up did not fail or warn; it simply shadowed the real database. Migrations and application writes went into the container's volume, while every other process on the machine kept talking to the native instance. Both were named explorer. Both looked correct in isolation. They diverged silently, and nothing about the setup surfaced the problem until the two were compared directly.

The Postgres service was removed from the compose file for this reason; Postgres on this machine runs natively, and DATABASE_URL in .env.local points at it directly. The lesson generalises beyond this one file: before running pnpm infra, check what is already listening on the ports the compose file will bind. A container that starts cleanly is not evidence that it is the only thing there.

Status

The Explorer has not yet been run end to end against live services. Treat this page as a deployment design, written from the application's source, configuration, and design document, rather than a runbook that has been exercised in production.

On this page