> ## 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.

# Isolated stacks

> Place each Service and its Datastore on one server, with a second independent pair on another server.

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/postgresql.webp?fit=max&auto=format&n=1QStfcSBIWWsf0OD&q=85&s=602ba67f317df779a4b8ec53b085c7c0" alt="Isolated stacks logo" aria-hidden="true" width="512" height="512" data-path="assets/database-logos/postgresql.webp" />

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="orders-postgres" theme={"system"}
  # .towbar/datastores/orders-postgres.datastore.yml
  id: orders-postgres
  name: Orders PostgreSQL
  type: postgres
  container:
    network: application
    networkAlias: orders-postgres
    resources:
      cpus: 1
      memory: 1g
  secrets:
    runtime:
      - POSTGRES_USER
      - POSTGRES_DB
      - POSTGRES_PASSWORD
  environments:
    production:
      server: 192.0.2.10
  ```

  ```yaml title="orders-api" theme={"system"}
  # .towbar/services/orders-api.service.yml
  id: orders-api
  name: Orders API
  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
  ```

  ```yaml title="billing-postgres" theme={"system"}
  # .towbar/datastores/billing-postgres.datastore.yml
  id: billing-postgres
  name: Billing PostgreSQL
  type: postgres
  container:
    network: application
    networkAlias: billing-postgres
    resources:
      cpus: 1
      memory: 1g
  secrets:
    runtime:
      - POSTGRES_USER
      - POSTGRES_DB
      - POSTGRES_PASSWORD
  environments:
    production:
      server: 192.0.2.11
  ```

  ```yaml title="billing-api" theme={"system"}
  # .towbar/services/billing-api.service.yml
  id: billing-api
  name: Billing API
  deployment:
    type: dockerfile
    context: .
    dockerfile: Dockerfile
  container:
    port: 3000
    network: application
  health:
    path: /health
  environments:
    production:
      server: 192.0.2.11
  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. Set `orders-api`'s `DATABASE_URL` to the `orders-postgres` alias and `billing-api`'s URL to `billing-postgres`. Save each value on its own Service; the two Docker networks are local to their servers.
5. Sync the repository, inspect the resolved configuration, deploy manually, and perform the verification below before enabling automation.

## Verify

Each Service reaches only its local Datastore; a same-name Docker network on two servers does **not** connect them.

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