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

# Connect a Sidecar

> Issue a token, point a Sidecar at the Control Plane, and let it pull its configuration.

A [Sidecar](/docs/core-concepts/sidecar) that knows nothing about a Control Plane reads its config file from disk and runs. Connecting it changes one thing: on boot, it asks the Control Plane what its configuration should be, and from then on the Control Plane owns that configuration.

<Tip>
  Deploying many Sidecars with automation? Skip the per-Sidecar token: [Service Accounts](/docs/setup/configuration/hoop-sidecar/service-accounts) lets each one authenticate with its Kubernetes or Google service account, and the Control Plane creates it on the first handshake.
</Tip>

***

## The handshake

```mermaid theme={"dark"}
sequenceDiagram
    participant S as Sidecar
    participant CP as Control Plane

    S->>S: boot, read local config
    S->>CP: ping, with token
    CP->>CP: identify the Sidecar from the token
    CP-->>S: your configuration
    S->>S: load into memory, start listeners
```

Four properties fall out of this shape, and they are the reason it looks like this:

* **No session to maintain.** The Sidecar loads its configuration into memory and keeps running. A Control Plane that goes down does not take running Sidecars with it. A listener using `require_review` is the exception: it files each held statement with the Control Plane, and denies that statement while the Control Plane is unreachable.
* **The token is the authentication.** There is no second credential and no certificate exchange. A Sidecar can use a [service account](/docs/setup/configuration/hoop-sidecar/service-accounts) instead of a token, never both.
* **Ordinary HTTP.** A plain request and a plain response. Nothing is tunnelled and nothing bidirectional stays open.
* **Neither side knows the other's physical address.** The Sidecar dials out; the Control Plane never dials in. No inbound firewall rule, no NAT traversal.

***

## Steps

<Steps>
  <Step title="Issue a token in the Control Plane">
    Create a Sidecar registration and copy the token it returns. It is shown once and stored only as a hash, so losing it means registering a new Sidecar.

    The token identifies this Sidecar and authenticates it — treat it as a credential.
  </Step>

  <Step title="Point the Sidecar at the Control Plane">
    Add the Control Plane's URL to the Sidecar's config file. The token is never written to disk — it goes on the command line or in an environment variable:

    ```yaml config.yaml theme={"dark"}
    control_plane_url: https://hoop.your-company.com
    ```

    ```bash theme={"dark"}
    hoop start sidecar --config config.yaml --token "<the token you copied>"
    ```

    Or supply both through the environment, which is the shape a Kubernetes deployment wants — mount the secret, set the variables, pass no arguments:

    ```bash theme={"dark"}
    export HOOP_CONTROL_PLANE_URL=https://hoop.your-company.com
    export HOOP_SIDECAR_TOKEN=<the token you copied>
    ```
  </Step>

  <Step title="Start it">
    ```bash theme={"dark"}
    hoop start sidecar --config config.yaml --token "<the token you copied>"
    ```

    On the first start the Control Plane holds no configuration for this Sidecar. It answers `412`, the Sidecar imports the document from your local file, and then serves what the Control Plane sends back. That import happens once.
  </Step>

  <Step title="Edit the configuration in the Control Plane from now on">
    The Control Plane owns the whole document after the import — listeners included. The two are never merged. Listeners left in the local file are ignored, and the Sidecar says so at startup:

    ```
    the config file's listeners are ignored: the control plane owns the running config
    ```

    `--config` becomes optional at this point. The URL and the token are enough to start.
  </Step>

  <Step title="Confirm what it resolved">
    The admin API reports the live configuration. It only runs when the configuration names an address for it, so the document the Control Plane serves needs an `admin` block — there is no default port:

    ```yaml theme={"dark"}
    admin:
      listen: 127.0.0.1:19000
    ```

    ```bash theme={"dark"}
    curl -s localhost:19000/healthz            # ok
    curl -s localhost:19000/config | python3 -m json.tool
    ```

    The Sidecar should also appear in the Control Plane's list, with a recent check-in.
  </Step>
</Steps>

***

## The license

The organization's license lives on the Control Plane, not on each Sidecar. It rides on the handshake response, and the response carries a header saying the Control Plane owns the decision:

```
hoop-sidecar-license-managed: true
```

While that header is present, the Sidecar's own license sources — the `--license` flag, `HOOP_LICENSE` and the config file's `license` key — are ignored, and it warns at startup that it is ignoring them. A Control Plane whose organization holds no license runs the free tier.

A `license` key authored on a Sidecar's configuration in the Control Plane is refused. One organization, one license.

***

## Staying in sync

A heartbeat repeats the handshake every minute.

A change that only touches rules — guardrails, masking, PII, OPA, a listener's analyzer block — is applied in place and logged as `control plane configuration applied`. Connections already open drain under the rules they were accepted with.

A change beyond the rules — listeners, audit, admin, log level — logs `restart to apply it` instead.

A failed heartbeat changes nothing. The Sidecar keeps serving the configuration and the license it last received.

Reviews do not ride on the heartbeat. A listener using [`require_review`](/docs/setup/configuration/hoop-sidecar/risk-analysis#holding-a-statement-for-a-person) calls the Control Plane once per held statement, and denies the statement when that call fails.

***

## Upgrading

Upgrade the Control Plane first, then the Sidecars.

A Sidecar refuses the whole configuration when one key in it is unknown to its version. From 1.210.0 the Control Plane knows what each Sidecar can read:

* It sends only the settings an admin set. Upgrading the Control Plane alone changes nothing an older Sidecar receives.
* A save that uses a setting, a rule type or a protocol that a connected Sidecar's version lacks is refused with `422`. The error names the Sidecar and the upgrade it needs. A Sidecar that never connected is checked at its first handshake instead.
* This covers every Sidecar from 1.162.0.

A Sidecar newer than its Control Plane runs, but cannot use a setting the Control Plane does not know. Its first import fails when its config file uses such a setting, and the Sidecar does not start. Do not roll the Control Plane back below a setting a stored configuration uses: the Control Plane then answers `500` to that Sidecar.

When a Sidecar refuses a configuration anyway:

| When | What happens |
| - | - |
| At startup | The Sidecar exits with an error that names the key. |
| On a heartbeat | The Sidecar keeps running its last rules and reports `refused`. |

The Sidecar page in the Control Plane shows the state, the Sidecar's version and the reason until a configuration applies. `Not applied` for longer than a few minutes means the Sidecar is restarting in a loop.

**Escape hatch.** An admin can hand the Sidecar back to its own config file:

```bash theme={"dark"}
curl -X PATCH https://hoop.your-company.com/api/sidecars/<name> \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"configuration": {"load_from_disk": true}}'
```

The Control Plane then sends only that flag and the license, and the Sidecar runs the config file it was started with. The switch deletes the Control Plane rules imported from this Sidecar that nothing else uses, and unbinds the rest. Send `{"configuration": {"load_from_disk": false}}` alone to hand it back: the stored configuration is cleared, and the Sidecar imports its config file again.

***

## Troubleshooting

| Symptom | Check |
| - | - |
| The Sidecar does not start at all | A first handshake that fails stops startup; there is no fallback to the local file. Check the URL, the token, and outbound HTTPS to the Control Plane host. |
| The Sidecar starts but never appears in the Control Plane | Outbound HTTPS to the Control Plane host. The Sidecar dials out; nothing dials in. |
| It warns that the config file's listeners are ignored | Expected once the Control Plane holds the configuration. Edit it there; the local file no longer holds it. |
| It warns that your license is ignored | Expected while the Control Plane manages licensing. Apply the license to the organization instead. |
| The admin API refuses the connection | `admin.listen` is not in the configuration the Control Plane serves. There is no default address. |
| Rules changed centrally but the Sidecar did not | Configuration is picked up on the heartbeat, not pushed. Wait up to a minute. A listener change needs a restart. |
| Authentication fails | The token is per-Sidecar. Reusing one across two Sidecars is not a supported shape. |
| The Sidecar page shows `Refused` or `Not applied` | The Sidecar's version cannot read the configuration. The page shows the reason. Upgrade the Sidecar, or remove the setting. See [Upgrading](#upgrading). |
| `more than one control plane credential is set` | Set either `HOOP_SIDECAR_TOKEN` (or `--token`) or `HOOP_SIDECAR_IDENTITY_TYPE`, not both. |
| Held statements are denied with `the review could not be filed` | Outbound HTTPS to the Control Plane host, and that the listener's `approval_rule` names an access request rule the Control Plane holds. A listener that holds statements fails closed. |

***

## Next

<CardGroup cols={2}>
  <Card title="Control Plane" icon="tower-control" href="/docs/core-concepts/control-plane">
    What it manages and why the protocol is this simple.
  </Card>

  <Card title="Install the Control Plane" icon="server" href="/docs/control-plane/install">
    Docker Compose, Kubernetes and AWS.
  </Card>

  <Card title="Config File Reference" icon="file-code" href="/docs/setup/configuration/hoop-sidecar/config-file#control-plane">
    Every source for `control_plane_url` and the credential, in precedence order.
  </Card>

  <Card title="Service Accounts" icon="id-badge" href="/docs/setup/configuration/hoop-sidecar/service-accounts">
    No token per Sidecar: one entry per cluster, Sidecars created on first contact.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.