Config

tablebelt.yaml

tablebelt init writes this file. Flag, then environment, then file, then default. Every key maps to TABLEBELT_ plus the path in upper case with underscores, for example TABLEBELT_LISTEN_ADDR and TABLEBELT_HOLD_BLOCK_TIMEOUT. Unknown keys are an error. Relative paths resolve against the config file's directory. A container can run from environment variables alone.

Search order for the file: $TABLEBELT_CONFIG, ./tablebelt.yaml, $XDG_CONFIG_HOME/tablebelt/tablebelt.yaml, /etc/tablebelt/tablebelt.yaml.

tablebelt.yaml
version: 1
database:
  name: prod
listen:
  addr: 127.0.0.1:5439
  auth: scram-sha-256
  users:
    - name: agent
      password_file: .tablebelt/agent.password
  allowed_options: [search_path, statement_timeout, lock_timeout, application_name]
  allow_plaintext_remote: false
  tls:
    mode: prefer
    cert_file: .tablebelt/tls.crt
    key_file: .tablebelt/tls.key
upstream:
  dsn_env: TABLEBELT_UPSTREAM_DSN
  admin_dsn_env: TABLEBELT_ADMIN_DSN
  max_conns: 50
  connect_timeout: 10s
hold:
  block_timeout: 60s
  pending_ttl: 24h
  approval_ttl: 30m
  max_pending: 100
snapshots:
  schema: tablebelt
  dsn_env: ""
  max_bytes_per_hold: 1GiB
  max_total_bytes: 5GiB
  retention: 14d
rules:
  builtin: default
  protected_schemas: [tablebelt]
  custom: []
limits:
  max_statement_bytes: 1MiB
admin:
  addr: 127.0.0.1:5440
  token_file: .tablebelt/admin.token
metrics:
  addr: ""
cloud:
  enabled: false
  url: https://app.tablebelt.com
  api_key_file: .tablebelt/cloud.key
  proxy_id_file: .tablebelt/proxy.id
  redact: full
  local_approvals: true
log:
  level: info
  format: json
  log_statements: held

Listen

Default address is 127.0.0.1:5439. Binding a non-loopback address requires client TLS require or listen.allow_plaintext_remote: true. Client auth is scram-sha-256, or trust only on loopback. Passwords come from password_env or password_file. The upstream DSN is upstream.dsn_env (default TABLEBELT_UPSTREAM_DSN) or dsn_file. Init refuses to write the upstream DSN into the YAML.

listen.allowed_options defaults to search_path, statement_timeout, lock_timeout, and application_name. Other startup -c options are rejected. That blocks session_replication_role=replica.

Holds and snapshots

hold.block_timeout defaults to 60 seconds (0 to 30 minutes). After that the client gets TB001 and the hold stays pending. An identical statement after approval runs once, within approval_ttl (default 30 minutes). pending_ttl defaults to 24 hours. max_pending defaults to 100.

Snapshots live in schema tablebelt on the admin DSN, or the upstream DSN if you do not set one. Default cap is 1 GiB per hold and 5 GiB total, kept for 14 days. See Restore.

Cloud

cloud.enabled: false is the Free default and makes no outbound calls. Pro and Team set url to https://app.tablebelt.com, store the API key in a 0600 file, and choose redact. local_approvals: false makes tablebelt approve refuse, so decisions happen in the dashboard, Slack, or email. Details are on Cloud.

Logs

JSON logs by default. Statement text is logged for held statements only (log_statements: held). Passwords, DSNs, and parameter values are not logged. metrics.addr empty means no /metrics listener. Set it to a loopback address, for example 127.0.0.1:9439, when you want Prometheus text.

The proxy reads config at start. Reloading the file on SIGHUP is not in this version. Restart the process after an edit.