TerraFlow

Docs · Install guide

Install inside your boundary

TerraFlow runs as containers on a host in your network or cloud account, with its own PostgreSQL and Redis. It can run fully offline from a signed bundle. This guide follows the kit in the TerraFlow repository (deploy/); your TerraFlow engineer runs it with you during a pilot.

What runs where

Everything below runs inside your boundary. Ports are bound to the loopback interface by default; put your own TLS reverse proxy in front of the data plane.

ServiceWhat it doesHolds prompt text?
dataplane
image terraflow, port 8080
The gateway (/v1), the engine and scheduler, the console API (/api) and the console itself (/console). Health at /health and /readyIn memory, while a request runs
postgres
PostgreSQL 16 with pgvector
Contracts and signed bundles, the hash-chained audit ledger, cost records, users, key hashes, documents for retrievalNo. Answers held for review are stored encrypted
redis
Redis 7, append-only persisted
Budgets, rate limits, sessions and the audit outboxNo
cache-redis
Redis 7, persistence off
The answer cache. It must be a separate, non-persisted instance: outside development the plane refuses to start without TERRAFLOW_CACHE_REDIS_URLYes, in memory only, never written to disk
Ollama (optional)Small local models for document search and request-type routing, so no request text leaves for that. On with TERRAFLOW_LOCAL_MODELS=ollama and TERRAFLOW_OLLAMA_URLIn memory, while it runs
otel-collector (optional profile)OpenTelemetry export; alert rules in deploy/alerts/terraflow.rules.ymlNo
mgmt (optional, connected mode)Publishes signed releases to connected planes. Never on a request's pathNo

Trust is anchored in files, not in the database: TERRAFLOW_KEYS_DIR holds the plane signing key, the pinned vendor release key (trusted-keys.json), the bundle version high-water mark and the ledger anchor. Keep it on its own volume and back it up with the database.

Prerequisites

The kit is Docker Compose on a VM. A Kubernetes package is not part of the current kit; the plane is one container image that uses external PostgreSQL and Redis, so it can be scheduled there with your own manifests.

Settings and secrets

Set these in the environment of a real installation. The Compose files fall back to development-only values, which the plane refuses outside development.

SettingValue
TERRAFLOW_ENVpilot in the offline stack, or production
TERRAFLOW_DB_PASSWORDPostgreSQL password
TERRAFLOW_REDIS_PASSWORDPassword for both Redis instances
TERRAFLOW_SESSION_SECRETAt least 32 characters; never the published default
TERRAFLOW_CACHE_REDIS_URLThe non-persisted cache Redis
TERRAFLOW_LOCAL_MODELSsimulated (default) or ollama
TERRAFLOW_PRIVATE_CIDRSYour private network ranges. A model marked private whose endpoint resolves outside them is refused when rules are activated
TERRAFLOW_SEED_PROFILEdemo, preview or none: what a new installation starts with. Agree it with your TerraFlow engineer

Install from the signed bundle

The offline bundle carries TerraFlow and its third-party images to a host with no network. Nothing is pulled or built on the host.

  1. Pin the vendor release key in keys/trusted-keys.json, using the public half you received out of band, not one from the bundle.
  2. Load and start:
    TERRAFLOW_TRUSTED_KEYS=keys/trusted-keys.json deploy/offline/load-bundle.sh /media/offline-bundle
    The script copies the bundle into a private (0700) directory and from then on uses only that copy. It runs terraflow offline verify (checksums, the manifest's signature by your pinned key, the image tar's own index, the Compose file), refuses any image the signed manifest does not list before docker load, checks every loaded image against the manifest, then starts the stack with docker compose --profile app up -d. Set TERRAFLOW_OFFLINE_NO_START=1 to stop before starting.
  3. Create the plane signing key once (it refuses to overwrite an existing key):
    docker compose exec dataplane terraflow keys init
  4. Import the release files you were given: terraflow release import <file>. A release is only ever staged; an approver activates it (see upgrades).
  5. Check it is up: curl -fsS http://127.0.0.1:8080/ready, then open https://<your-terraflow-host>/console/.

On every start the container applies database migrations (terraflow db upgrade) and the seed profile (terraflow seed), then serves. The seed only fills a new installation: on one that has activated rules it never creates, approves or activates anything, and everything it writes is in the audit ledger.

Evaluation install (with network)

For a first look on a connected machine, deploy/compose/docker-compose.yml builds the image from the repository and runs PostgreSQL, both Redis instances and the data plane, all on loopback ports:

cp .env.example .env && set -a && . ./.env && set +a
docker compose -f deploy/compose/docker-compose.yml --profile app up -d --build
# console: http://127.0.0.1:8080/console/

This file runs with TERRAFLOW_ENV=development and the demo estate. Use it to evaluate, not for real data.

Connecting models and keys

Each model is a card in the model catalogue: where it runs (hosting, region, jurisdiction), the most sensitive data class it may receive, the sensitive types it may receive, its context window and its price. Adapters exist for openai_compat, anthropic, azure_openai and bedrock.

Keys are never written into TerraFlow's files or database. A card holds a reference: env:NAME (an environment variable) or file:/absolute/path (a file). Anything else is rejected when the catalogue is loaded. Have your vault (for example a vault agent or your platform's secret mount) deliver the secret as that variable or file.

- id: custcloud-in-standard
  display_name: Azure OpenAI deployment, customer cloud (India)
  adapter: azure_openai
  endpoint_ref: env:AZURE_OPENAI_ENDPOINT        # a reference, not the endpoint
  credential_ref: env:AZURE_OPENAI_API_KEY       # a reference, not the key
  model: <your-deployment-name>
  hosting: customer_cloud
  region: in-central
  jurisdiction: IN
  max_data_class: confidential
  allowed_sensitive_types: [pii]
  context_window: 128000

Shape from seed/capabilities.yaml; validated by terraflow/core/schemas/capability.py.

First rules

Rules are a contract per workload (one AI use case). A contract states:

Write or edit a contract on the console's Workloads screen. A different person must approve it before it runs. Approved contracts, the policy and the model catalogue reach the engine only as a bundle signed with the plane's key; an invalid or expired bundle fails closed. Check the active bundle with:

docker compose exec dataplane terraflow bundle verify
# bundle <version> OK — signer <key id>, expires <date>, <n> contracts, <n> capabilities

To see why a request was allowed or refused, open it in the console's Routing screen, or use terraflow policy explain.

Verifying the audit chain

Every outcome, answered, cached, refused or failed, is appended to a hash-chained ledger, sealed in signed checkpoints by the plane key. It holds ids, reason codes, costs and timings, never prompt or answer text. Verify it from the command line, or from the console's Evidence screen:

docker compose exec dataplane terraflow ledger verify
# ledger OK — <n> events, <n> signed checkpoints

A failure names the first bad sequence number and the reason:

ReasonMeaning
UNSIGNED_TAILEvents that no signed checkpoint covers
CHECKPOINT_MISSINGA signed checkpoint was deleted
LEDGER_TRUNCATEDThe ledger is shorter than the local anchor records

If the ledger no longer holds its anchor, the plane refuses every request with EVIDENCE_UNAVAILABLE before anything is sent to a model. If you deliberately reset the database, also remove <TERRAFLOW_KEYS_DIR>/ledger-anchor.

You can also fetch the audit events of one request through the API: GET /v1/requests/{id}/evidence.

Upgrades

Sources: deploy/offline/, deploy/compose/docker-compose.yml, Dockerfile, docs/runbooks/offline-install.md, terraflow/cli.py, terraflow/config/settings.py.