> ## Documentation Index
> Fetch the complete documentation index at: https://www.towbar.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Event analytics

> Ingest Service + clickhouse Datastore on one private network.

export const UseCaseNavigation = () => {
  useLayoutEffect(() => {
    const storageKey = "towbar-use-case-open-groups";
    const groupSelector = "#sidebar li[data-title] > button[aria-expanded]";
    let restoring = false;
    let frame = 0;
    function storedGroups() {
      try {
        return new Set(JSON.parse(sessionStorage.getItem(storageKey) || "[]"));
      } catch {
        return new Set();
      }
    }
    function save(groups) {
      try {
        sessionStorage.setItem(storageKey, JSON.stringify([...groups]));
      } catch {}
    }
    function groupName(button) {
      return button.parentElement?.dataset.title;
    }
    function rememberOpenGroups() {
      const groups = new Set();
      for (const button of document.querySelectorAll(groupSelector)) {
        if (button.getAttribute("aria-expanded") === "true") {
          const name = groupName(button);
          if (name) groups.add(name);
        }
      }
      save(groups);
    }
    function restoreOpenGroups() {
      frame = 0;
      const groups = storedGroups();
      if (!groups.size) return;
      restoring = true;
      for (const button of document.querySelectorAll(groupSelector)) {
        if (groups.has(groupName(button)) && button.getAttribute("aria-expanded") === "false") {
          button.click();
        }
      }
      restoring = false;
    }
    function scheduleRestore() {
      if (!frame) frame = requestAnimationFrame(restoreOpenGroups);
    }
    function onClick(event) {
      if (restoring || !(event.target instanceof Element)) return;
      const button = event.target.closest(groupSelector);
      if (button) {
        const name = groupName(button);
        if (!name) return;
        const groups = storedGroups();
        if (button.getAttribute("aria-expanded") === "true") groups.delete(name); else groups.add(name);
        save(groups);
        return;
      }
      if (event.target.closest('a[href^="/docs/use-cases/"]')) {
        rememberOpenGroups();
      }
    }
    const observer = new MutationObserver(scheduleRestore);
    document.addEventListener("click", onClick, true);
    observer.observe(document.getElementById("sidebar") || document.body, {
      childList: true,
      subtree: true
    });
    scheduleRestore();
    return () => {
      document.removeEventListener("click", onClick, true);
      observer.disconnect();
      cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<UseCaseNavigation />

<img className="towbar-doc-brand-logo" src="https://mintcdn.com/avgeek/1QStfcSBIWWsf0OD/assets/database-logos/clickhouse.webp?fit=max&auto=format&n=1QStfcSBIWWsf0OD&q=85&s=36934d85e2293c6ab0955b0e71a3223c" alt="Event analytics logo" aria-hidden="true" width="575" height="512" data-path="assets/database-logos/clickhouse.webp" />

**Related reading:** [ClickHouse](/docs/databases/clickhouse).

These are separate files in one repository. A shared Docker network works only when the workloads run on the same server; Towbar does not automatically order their deployments or copy secret values between them.

## Towbar manifests

<CodeGroup>
  ```yaml title=".towbar/datastores/event-analytics-clickhouse.datastore.yml" theme={"system"}
  id: event-analytics-clickhouse
  name: Event analytics ClickHouse
  type: clickhouse
  container:
    network: application
    networkAlias: event-analytics-clickhouse
    resources:
      cpus: 1
      memory: 1g
  secrets:
    runtime:
      - CLICKHOUSE_USER
      - CLICKHOUSE_PASSWORD
      - CLICKHOUSE_DB
  environments:
    production:
      server: 192.0.2.10
  ```

  ```yaml title=".towbar/services/event-analytics-api.service.yml" theme={"system"}
  id: event-analytics-api
  name: Event analytics
  deployment:
    type: dockerfile
    context: .
    dockerfile: Dockerfile
  container:
    port: 3000
    network: application
  health:
    path: /health
  environments:
    production:
      server: 192.0.2.10
  secrets:
    runtime:
      - DATABASE_URL
  ```
</CodeGroup>

## Configure

1. Register and prepare the example server or servers, connect the repository, and map `production` to the branch containing these files.
2. Save each Datastore credential value under **Datastore → Settings → Secrets**, deploy it first, and verify engine readiness before starting a dependent Service.
3. Save the Service's declared keys in its selected environment, or enable and authorize the selected [external-secret provider](/docs/secrets/external). Do not commit the values.
4. Point each Service connection secret at `event-analytics-clickhouse` on the database engine's private port. The server and network must match; credentials are saved separately for each Service.
5. Create an application-specific user with only the required privileges after the database is ready. Save its connection URL on the consuming Service instead of reusing the Datastore's administrator credentials.
6. Sync the repository, inspect the resolved configuration, deploy manually, and perform the verification below before enabling automation.

## Verify

Measure ingest, query, and disk usage before sizing the server.

For field constraints, see [Service manifest](/docs/services/manifest) and [Datastore manifest](/docs/datastores/manifest).
