Our engine never sees your warehouse password
datahashi

Self-host the community engine

Run the open-source datahashi engine locally or in Docker. No external database.

Updated

This page runs the headless engine (hashi-engine). It reads YAML from a folder. It has no control plane, no database, and no tenants. The community binary is hashi. That binary adds a console and a SQLite store. Set ENGINE_MODEL_DIR on hashi to use this same folder.

flowchart TD
    E["hashi-engine<br/>model dir $ENGINE_MODEL_DIR:<br/>model.yaml (entities, dimensions, metrics)<br/>warehouse.yaml (runner URL and key)<br/>policy.yaml (optional data roles and RLS)<br/>auth.yaml (optional JWT auth binding)"] -->|"parameterized SQL over HTTP"| QR["query-runner<br/>Postgres · MySQL · Snowflake · BigQuery · and five more"]

To change the model, edit the YAML files. Then restart the process.

Folder structure

Create a directory (for example ./model) with these files.

1. model.yaml (required)

Defines entities, dimensions, measures, relationships, and metrics:

source: local_warehouse

entities:
  - name: Customer
    table: public.customers
    primary_key: id
    dimensions:
      - name: id
        type: string
      - name: region
        type: string
    measures:
      - name: customer_count
        expr: id
        agg: count

metrics:
  - name: total_customers
    expr: customer_count

2. warehouse.yaml (required)

Tells the engine where Query Runner lives. Both values are environment references, not literals. Mode 1 sets RUNNER_URL to a loopback address. Mode 2 sets it to the runner service.

backend: postgresql
runner_url: "env:RUNNER_URL"
runner_key: "env:RUNNER_KEY"

3. policy.yaml (optional)

Data roles and row-level security. If you omit this file, one role named default grants every entity, every field, and every metric.

roles:
  public:
    grants:
      - kind: metric
        target: "*"
      - kind: entity
        target: "*"
        fields: ["*"]

4. config.yaml (Query Runner)

Save this file next to model/. It sets listen, one policy, one key, and one warehouse. Allow every column the model reads.

listen: ":8443"

policies:
  local:
    default: deny
    tables:
      public.customers:
        effect: allow
        fields:
          allow: [id, region]

keys:
  - id: local
    sha256: "95ff67bab710562b07e0e5c6ee95f46a46f856bf10a0773bf8bd6f37cc8fd67d"
    policy: local

warehouses:
  local_warehouse:
    engine: postgresql
    dsn_env: QUERY_RUNNER_WAREHOUSE_DSN
    schemas: [public]

The hash above is for the key local-runner-key. When you change the key, make a new hash. Copy the first field into keys[].sha256.

printf '%s' 'local-runner-key' | sha256sum

Include mode replaces listen with the loopback address from RUNNER_URL.

These names must match:

  • Model source equals the runner warehouses key.
  • runner_source in warehouse.yaml replaces source when you set it.
  • backend in warehouse.yaml equals the runner warehouse engine.
  • The sha256 of the runner key equals keys[].sha256.

Get the images

Query Runner is published as ghcr.io/datahashi/query-runner. The community engine image is not published yet. Build it from source.

From the hashi repository root:

GITHUB_TOKEN=... docker build --secret id=github_token,env=GITHUB_TOKEN -t hashi:community .

The hashi build needs read access to datahashi/query-runner through GITHUB_TOKEN.

Mode 1 uses the query-runner binary inside hashi:community. Mode 2 runs the published ghcr.io/datahashi/query-runner:0.5.2 image.

Deploy

Run both modes from the directory that contains model/ and config.yaml. Replace QUERY_RUNNER_WAREHOUSE_DSN with your warehouse.

Mode 1: Single container (include-runner)

hashi-engine starts query-runner as a child on the loopback address in RUNNER_URL. Mount the runner config. Set QUERY_RUNNER_CONFIG. The warehouse password stays in the child environment.

docker run -d \
  --name hashi-engine \
  -p 8081:8081 \
  -v "$(pwd)/model:/etc/hashi/model:ro" \
  -v "$(pwd)/config.yaml:/etc/query-runner/config.yaml:ro" \
  -e ENGINE_MODEL_DIR=/etc/hashi/model \
  -e ENGINE_API_KEY=local-secret \
  -e ENGINE_INCLUDE_RUNNER=true \
  -e QUERY_RUNNER_CONFIG=/etc/query-runner/config.yaml \
  -e QUERY_RUNNER_WAREHOUSE_DSN="postgres://user:pass@host:5432/analytics?sslmode=require" \
  -e RUNNER_URL=http://127.0.0.1:8443 \
  -e RUNNER_KEY=local-runner-key \
  hashi:community \
  /usr/local/bin/hashi-engine

Mode 2: Docker Compose (separate containers)

Separate the engine from Query Runner. The engine container then has no warehouse password. Set ENGINE_RUNNER_ALLOW_PRIVATE_EGRESS because the runner is on a private Docker network.

services:
  query-runner:
    image: ghcr.io/datahashi/query-runner:0.5.2
    ports:
      - "8443:8443"
    environment:
      QUERY_RUNNER_WAREHOUSE_DSN: "postgres://user:pass@host:5432/analytics?sslmode=require"
    volumes:
      - ./config.yaml:/etc/query-runner/config.yaml:ro
    restart: unless-stopped

  engine:
    image: hashi:community
    command: ["/usr/local/bin/hashi-engine"]
    ports:
      - "8081:8081"
    environment:
      ENGINE_MODEL_DIR: /etc/hashi/model
      ENGINE_API_KEY: "prod-secret-key"
      ENGINE_INCLUDE_RUNNER: "false"
      ENGINE_ROLLUP_STORE_DIR: /var/lib/hashi/rollups
      ENGINE_RUNNER_ALLOW_PRIVATE_EGRESS: "true"
      RUNNER_URL: http://query-runner:8443
      RUNNER_KEY: local-runner-key
    volumes:
      - ./model:/etc/hashi/model:ro
      - ./rollups:/var/lib/hashi/rollups
    depends_on:
      - query-runner
    restart: unless-stopped

Verify the deployment

REST query

Use the ENGINE_API_KEY from the mode you started. The command below uses the Mode 2 key. Mode 1 uses local-secret.

curl http://localhost:8081/v1/query \
  -H "Authorization: Bearer prod-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"metrics": ["total_customers"]}'

PostgreSQL wire (psql)

The compose above leaves SQL wire off. Set ENGINE_SQL_ADDR and the two TLS file paths. Mount ./tls on the engine service. SQL wire refuses plaintext. Put a PEM certificate and key in ./tls before you connect. dbname is any string. FROM names an entity.

PGPASSWORD=prod-secret-key psql "host=localhost port=5432 user=local dbname=analytics sslmode=require" -c \
  'SELECT total_customers FROM "Customer";'

MCP endpoint

Point any MCP-compatible agent to:

http://localhost:8081/mcp

Next: REST API · Environment variables.