kimo
GuideBeginner10 min

Install Kimo Bridge with Docker

To install Kimo Bridge with Docker, create a read-only database role, generate an enrollment token in Kimo, and run the ghcr.io/getkimo/bridge container on a host that can reach your database. The bridge dials out to Kimo on port 443 — you open no inbound ports — and nothing is queryable until you allow specific tables in a local policy file. Plan on about fifteen minutes from a clean host to your first live chart.

Arno Visser · Solutions architect
10 min read

At a glance

Level
Beginner
Time
10 min

Prerequisites

  • A Linux host with Docker Engine (and Compose v2) that can reach your database
  • Outbound HTTPS (TCP 443) from that host to Kimo's regional bridge endpoint
  • Administrator access to your database to create a read-only role
  • Workspace admin rights in Kimo to create an enrollment token

You will end up with

A hardened Kimo Bridge container connected to Kimo over an outbound-only tunnel, serving one database in Bridge mode under a deny-by-default policy, verified end to end.

What do you need before you start?

Kimo Bridge is the small package that lets Kimo query a database on your infrastructure without copying it to the cloud. If you want the architectural background first, read Your Data, Your Rules or the short Kimo Bridge overview. This guide is the hands-on part.

RequirementMinimumNotes
HostLinux x86_64 or arm64, 1 vCPU, 1 GB RAMA small VM in the same subnet as the database is ideal
Container runtimeDocker Engine with Compose v2Podman works with the same flags
EgressTCP 443 to bridge.eu.getkimo.com or bridge.us.getkimo.comPlus DNS. An HTTPS proxy is supported via HTTPS_PROXY
Database accessTCP from the host to the database portPrefer a read replica
Kimo roleWorkspace adminNeeded to create enrollment tokens
Inbound rulesNoneThe bridge never listens on a public interface
Kimo Bridge host requirements for a Docker install.

Step 1: Create a read-only database role

Do this before anything else. The bridge enforces its own policy, but the database role is the control that holds even if every other layer failed. Grant the narrowest access that covers the tables your dashboards need — usually a reporting or analytics schema, not the whole database.

PostgreSQL

PostgreSQL can attach default_transaction_read_only and statement_timeout to a role with ALTER ROLE … SET, so every session the bridge opens starts read-only and no statement can run forever.27

PostgreSQL — run as an administrator
sql
CREATE ROLE kimo_bridge LOGIN PASSWORD 'use-a-generated-secret';
ALTER ROLE kimo_bridge SET default_transaction_read_only = on;
ALTER ROLE kimo_bridge SET statement_timeout = '30s';
ALTER ROLE kimo_bridge SET idle_in_transaction_session_timeout = '60s';
ALTER ROLE kimo_bridge CONNECTION LIMIT 10;

GRANT CONNECT ON DATABASE app TO kimo_bridge;
GRANT USAGE ON SCHEMA analytics TO kimo_bridge;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO kimo_bridge;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
  GRANT SELECT ON TABLES TO kimo_bridge;

If your tables use row-level security, make sure the bridge role is not a superuser, does not have BYPASSRLS and does not own the tables: all three bypass RLS policies.3 The Postgres connector documentation covers SSL modes and replica settings in more detail.

MySQL and MariaDB

MySQL can require TLS for the account and cap its connections and queries per hour directly in CREATE USER.4 A database-level SELECT grant applies to every table in that database, so use a dedicated reporting database or grant per table if the database also holds sensitive tables.5

MySQL — run as an administrator
sql
CREATE USER 'kimo_bridge'@'10.20.0.%'
  IDENTIFIED BY 'use-a-generated-secret'
  REQUIRE SSL
  WITH MAX_USER_CONNECTIONS 10 MAX_QUERIES_PER_HOUR 20000;

GRANT SELECT ON reporting.* TO 'kimo_bridge'@'10.20.0.%';
SHOW GRANTS FOR 'kimo_bridge'@'10.20.0.%';

Step 2: Get an enrollment token

  1. Step 1:

    Open the Bridge console

    In Kimo, go to Bridge and choose New bridge. Give it a name that says where it runs, such as fra-prod-01.

  2. Step 2:

    Choose a group

    Bridges in the same group serve the same sources and share load. Start with one bridge; you can add a second later for high availability.

  3. Step 3:

    Copy the token

    Kimo shows a one-time enrollment token beginning with kbt_. It is valid for 24 hours and for a single enrollment. Treat it like a password until it is used.

The token is not a long-term credential. On first start the bridge generates a private key on your host, uses the token once to obtain a short-lived client certificate for that key, and from then on authenticates with the certificate. The key never leaves the host. The security model explains the full chain.

Step 3: Write kimo-bridge.yaml

The configuration file declares your sources and the policy for each. Create a directory for it and for secrets on the host:

Prepare the host
bash
sudo mkdir -p /opt/kimo-bridge/secrets
cd /opt/kimo-bridge

# One file per secret, readable only by root and the container user
printf '%s' 'kbt_live_...' | sudo tee secrets/token >/dev/null
printf '%s' 'postgres://kimo_bridge:...@10.20.0.15:5432/app?sslmode=verify-full' \
  | sudo tee secrets/crm_dsn >/dev/null
sudo chown -R 10001:10001 secrets && sudo chmod 400 secrets/*
/opt/kimo-bridge/kimo-bridge.yaml
yaml
bridge:
  name: fra-prod-01
  group: prod-eu
  region: eu

sources:
  - id: crm_pg
    type: postgres
    dsn_file: /run/secrets/crm_dsn
    mode: bridge            # live pushdown; set to "cloud" to sync instead
    cache_ttl: 5m           # 0s disables result caching on Kimo's side
    policy:
      default: deny
      tables:
        analytics.accounts:
          columns: [id, region, plan, mrr_cents, created_at, churned_at]
        analytics.invoices:
          columns: [id, account_id, amount_cents, currency, paid_at]
      min_group_size: 5
      limits:
        max_rows: 50000
        timeout: 30s
        rate: 10/s

audit:
  path: /var/lib/kimo-bridge/audit.jsonl
  mirror_to_kimo: true

Start narrow. Allow the columns your first dashboard needs, ship it, then widen deliberately. Column names that are not allow-listed cannot appear in any query — Kimo's semantic layer simply will not offer them.

Step 4: Run the container

The quickest path is a single docker run. The flags below are the hardened defaults we recommend: a read-only root filesystem, all Linux capabilities dropped, no privilege escalation, a non-root user, and an automatic restart policy — all standard docker run options.1

One-liner (hardened)
bash
docker run -d --name kimo-bridge \
  --restart unless-stopped \
  --read-only --cap-drop ALL \
  --security-opt no-new-privileges \
  --user 10001:10001 \
  -e KIMO_BRIDGE_TOKEN_FILE=/run/secrets/token \
  -e KIMO_BRIDGE_CONFIG=/etc/kimo-bridge/kimo-bridge.yaml \
  -v /opt/kimo-bridge/kimo-bridge.yaml:/etc/kimo-bridge/kimo-bridge.yaml:ro \
  -v /opt/kimo-bridge/secrets:/run/secrets:ro \
  -v kimo-bridge-data:/var/lib/kimo-bridge \
  -p 127.0.0.1:8080:8080 \
  ghcr.io/getkimo/bridge:1.4.2

The kimo-bridge-data volume holds the bridge's identity (its key and current certificate) and the local audit log; it is the only writable path. Port 8080 serves /healthz, /readyz and Prometheus /metrics, bound to localhost only.

With Docker Compose

For anything you will keep, prefer Compose. Docker recommends secrets over environment variables for passwords and keys, because environment variables are often available to all processes and can end up in logs; Compose mounts each secret as a file under /run/secrets/.6

/opt/kimo-bridge/compose.yaml
yaml
services:
  bridge:
    image: ghcr.io/getkimo/bridge:1.4.2
    restart: unless-stopped
    read_only: true
    user: '10001:10001'
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    environment:
      KIMO_BRIDGE_TOKEN_FILE: /run/secrets/token
      KIMO_BRIDGE_CONFIG: /etc/kimo-bridge/kimo-bridge.yaml
      KIMO_BRIDGE_LOG_LEVEL: info
    volumes:
      - ./kimo-bridge.yaml:/etc/kimo-bridge/kimo-bridge.yaml:ro
      - data:/var/lib/kimo-bridge
    secrets: [token, crm_dsn]
    ports:
      - '127.0.0.1:8080:8080'

secrets:
  token:
    file: ./secrets/token
  crm_dsn:
    file: ./secrets/crm_dsn

volumes:
  data: {}
Start it
bash
cd /opt/kimo-bridge && docker compose up -d
docker compose logs -f bridge

A healthy first start logs four lines in order: generated identity key, enrolled as fra-prod-01 in group prod-eu, tunnel established region=eu tls=1.3, and source crm_pg ready mode=bridge. Once enrolled, delete the token file; the bridge no longer needs it.

Behind an HTTPS proxy

If outbound traffic must go through a corporate proxy, add -e HTTPS_PROXY=http://proxy.internal:3128 (and NO_PROXY for your database host). The proxy must allow CONNECT to the bridge hostname on port 443 without terminating TLS — the bridge authenticates Kimo end to end and will refuse a re-signed certificate.

Without Docker: the static binary

Hosts that cannot run containers can use the kimo-bridge binary, published for Linux amd64 and arm64 alongside a SHA-256 checksum file. It reads the same configuration and secrets, so everything above applies unchanged. Run it under a dedicated system user with a hardened systemd unit:

Install the binary and run it with systemd
bash
sudo useradd --system --home /var/lib/kimo-bridge --shell /usr/sbin/nologin kimo-bridge
sudo install -m 0755 kimo-bridge /usr/local/bin/kimo-bridge

sudo tee /etc/systemd/system/kimo-bridge.service >/dev/null <<'UNIT'
[Unit]
Description=Kimo Bridge
After=network-online.target
Wants=network-online.target

[Service]
User=kimo-bridge
Environment=KIMO_BRIDGE_CONFIG=/opt/kimo-bridge/kimo-bridge.yaml
Environment=KIMO_BRIDGE_TOKEN_FILE=/opt/kimo-bridge/secrets/token
ExecStart=/usr/local/bin/kimo-bridge run
Restart=on-failure
StateDirectory=kimo-bridge
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
UNIT

sudo systemctl daemon-reload && sudo systemctl enable --now kimo-bridge

With the binary, the secret paths in kimo-bridge.yaml point at /opt/kimo-bridge/secrets/… instead of /run/secrets/…, and the files must be owned by the kimo-bridge user. Every command in the next step works the same way without the docker exec kimo-bridge prefix.

Step 5: Verify the installation

Run the built-in diagnostics inside the container. doctor checks DNS, egress, certificate validity, the tunnel, each source connection and the policy file.

Diagnostics
bash
docker exec kimo-bridge kimo-bridge doctor
docker exec kimo-bridge kimo-bridge policy check
docker exec kimo-bridge kimo-bridge test-source crm_pg \
  --sql "select plan, count(*) from analytics.accounts group by 1"
Expected doctor output
bash
ok  dns        bridge.eu.getkimo.com resolved
ok  egress     443/tcp reachable (proxy: none)
ok  identity   certificate valid, renews in 21h
ok  tunnel     connected, TLS 1.3, mutual auth
ok  source     crm_pg postgres 16.4, role read-only, statement_timeout=30s
ok  policy     2 tables, 11 columns allowed, default deny

Verification checklist

  • The bridge shows Connected in the Bridge console, with the expected name and group.
  • The source crm_pg appears under Connectors with the mode you chose.
  • kimo-bridge test-source returns rows for an allowed query and refuses a query on a column that is not allow-listed.
  • An attempted write (delete from analytics.accounts) is rejected by the bridge and logged as denied.
  • The audit entries appear in both /var/lib/kimo-bridge/audit.jsonl and Activity.
  • ss -tlnp on the host shows the bridge listening only on 127.0.0.1:8080.

Then build something real: open Explore, pick the bridged source, and chart accounts by plan. If you are setting up board reporting, the SaaS metrics template works on bridged sources unchanged.

Should this source use Bridge or Cloud mode?

Each source picks its own mode, and you can change it later by editing one line. Use Bridge (mode: bridge) for sensitive data and anything with residency commitments: Kimo pushes each query down and keeps only short-lived result caches. Use Cloud (mode: cloud) for event data and heavy exploratory work: the bridge syncs tables incrementally through the same outbound tunnel, and Kimo keeps history. The trade-offs are covered in Cloud, hybrid or bridge.

Upgrade, rotate and revoke

TaskCommand or actionEffect
Upgradedocker compose pull && docker compose up -dNew image, same identity; tunnel reconnects in seconds
Reload policydocker exec kimo-bridge kimo-bridge reloadApplies kimo-bridge.yaml changes without dropping the tunnel
Rotate DB passwordUpdate secrets/crm_dsn, then reloadNew connections use the new secret
Pausedocker exec kimo-bridge kimo-bridge pauseCloses the tunnel and cancels in-flight queries until resume
RevokeBridge console → RevokeCertificate revoked; bridge cannot reconnect; Kimo caches purged
Removedocker compose down -vDeletes the container, identity and local audit log
Day-two operations for a Docker-installed bridge.

Troubleshooting

SymptomLikely causeFix
enrollment failed: token expired or already usedToken older than 24 hours, or a previous start already enrolledCreate a new token; if the volume holds an identity, the bridge does not need one
tunnel: dial tcp ...:443: i/o timeoutEgress blocked or a proxy is requiredAllow TCP 443 to the regional hostname, or set HTTPS_PROXY
tls: certificate signed by unknown authorityA TLS-inspecting proxy is re-signing trafficExempt the bridge hostname from inspection; mutual TLS cannot work through interception
region mismatch: workspace is usConfig says region: eu for a US workspaceSet the region that matches your workspace
source crm_pg: permission denied for schema analyticsMissing USAGE on the schemaGrant USAGE as in Step 1
Queries return denied: column not allowedPolicy works as designedAdd the column to the allow-list if it is genuinely needed, then reload
read-only file system at startData volume missing with --read-onlyMount a volume at /var/lib/kimo-bridge
Dashboards slow during business hoursBridge points at the primary databasePoint the DSN at a read replica; lower rate; consider mode: cloud for heavy tables
Common Kimo Bridge installation errors and their fixes.

Still stuck? Run kimo-bridge doctor --bundle to produce a redacted diagnostics archive (no credentials, no query results) and attach it to a support request from the contact page.

Sources

7 references
  1. docker container run (opens in a new tab)
    Docker Documentationdocs.docker.com

    Options --read-only, --cap-drop, --security-opt, --restart and --user.

  2. Client Connection Defaults (opens in a new tab)
    PostgreSQL Documentationpostgresql.org

    statement_timeout, default_transaction_read_only and idle_in_transaction_session_timeout.

  3. Row Security Policies (opens in a new tab)
    PostgreSQL Documentationpostgresql.org

    Superusers, BYPASSRLS roles and table owners bypass row security.

  4. CREATE USER Statement (opens in a new tab)
    MySQL 8.4 Reference Manualdev.mysql.com

    REQUIRE SSL and per-account resource limits.

  5. GRANT Statement (opens in a new tab)
    MySQL 8.4 Reference Manualdev.mysql.com

    Database-level privileges apply to all objects in the database.

  6. Manage secrets securely in Docker Compose (opens in a new tab)
    Docker Documentationdocs.docker.com

    Secrets mounted under /run/secrets; environment variables discouraged for sensitive values.

  7. ALTER ROLE (opens in a new tab)
    PostgreSQL Documentationpostgresql.org

    Role-specific session defaults (ALTER ROLE … SET) and CONNECTION LIMIT.

External sources were accessed at the time of writing. Kimo product details, customers and figures in examples are illustrative unless a source is cited.

0%

Mark as done

0 of 9 sections done

Frequently asked questions

Do I have to open any inbound ports?

No. The bridge makes one outbound TLS connection to Kimo on TCP 443. The only port it listens on is the local health and metrics port, which you bind to 127.0.0.1.

Can I pass the token and DSN as environment variables?

Yes, KIMO_BRIDGE_TOKEN and plain dsn values work, but we recommend files mounted as secrets. Environment variables are visible to every process in the container and tend to end up in logs and support bundles.

How much load does the bridge put on my database?

Only what your dashboards ask for, bounded by the rate limit in the policy and the statement timeout on the role. Because aggregations are pushed down, typical dashboard queries are short. Point the bridge at a read replica for production.

What happens if the host restarts?

With restart set to unless-stopped and the data volume in place, the bridge restarts, reuses its identity and reconnects without a new token. For no-downtime restarts, run a second bridge in the same group.

Can one bridge serve several databases?

Yes. Add more entries under sources, each with its own DSN, mode and policy. Use separate bridges only when databases sit in network segments that should not be connected.

Put it to work

Skip the setup — start from a working version.

Open the app and follow along with the steps in this guide.

All resources
GuideAdvanced
All

Deploy Kimo Bridge on Kubernetes

Helm chart, secrets, high availability and network policies for production.

Arno Visser
10 min read
GuideIntermediate
All

The Kimo Bridge security model

What leaves your network, what never does, and how every query is authorized and audited.

Rhea Patel
10 min read
Whitepaper
All

Your Data, Your Rules

The hybrid analytics architecture behind Kimo Bridge: live query pushdown, optional cloud sync, and zero-trust by default.

Arno Visser
22 pages

Your data officer is ready.

Connect a source — or install Kimo Bridge and keep data on your servers — then ask a question and get an answer you can audit.