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

# Install and run the agent on GCP

> Stand up a production formae agent on GCP with the formae-bootstrap installer, then connect your CLI to it.

formae runs as a client and an agent. The agent lives in your infrastructure,
executes changes, and keeps state in sync with your cloud; you use your local CLI
to provision it once, then [point the CLI at it](/documentation/guides/manage-profiles)
with a profile and hand off. See [Architecture](/documentation/concepts/architecture)
for the client/agent model.

The recommended path is the open-source
[`formae-bootstrap`](https://github.com/platform-engineering-labs/formae-bootstrap)
installer. One apply stands up the whole agent (a VPC, a Cloud SQL for PostgreSQL
datastore, and a GCE VM running the agent container), secure by default.

<Note>
  This guide covers the GCP bootstrap path. For other clouds, see
  [Install on AWS](/documentation/guides/install-agent) and
  [Install on Azure](/documentation/guides/install-agent-azure).
</Note>

## Choose an access mode

Compute is always a GCE VM on Container-Optimized OS, running the agent container
plus a Cloud SQL Auth Proxy sidecar. `--access` decides how clients reach it. Basic
auth is on in both modes.

* **Tailscale (`tailnet`, default)**: private, reachable only over your Tailscale
  tailnet; the agent serves a trusted `*.ts.net` certificate. No public ingress.
* **Public (`public`)**: a global external HTTPS load balancer fronts the agent.
  Requires a domain you own.

## Prerequisites

* The formae CLI, matching the agent image version. If you have not installed it,
  see the [Quick start](/documentation/get-started/quickstart).

* A GCP project, with the required APIs enabled:

  ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
  gcloud services enable compute.googleapis.com sqladmin.googleapis.com \
    secretmanager.googleapis.com --project <project>
  ```

* Local credentials for the formae GCP plugin: either
  `gcloud auth application-default login` (user ADC, simplest) or
  `export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json`. Restart the agent
  after setting them so the plugin picks them up.

* Tailscale (tailnet mode only): a reusable auth key tagged `tag:formae`, with HTTPS
  certificates enabled on your tailnet.

Clone the installer:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
git clone https://github.com/platform-engineering-labs/formae-bootstrap.git
cd formae-bootstrap
```

## Install

<Tabs>
  <Tab title="Tailscale (tailnet)">
    You also need a Tailscale tailnet with a `tag:formae` tag, HTTPS certificates
    enabled, and a reusable auth key carrying that tag:

    * Sign in at [login.tailscale.com/start](https://login.tailscale.com/start)
      with an identity provider (the free Personal plan is enough) and note your
      tailnet name (e.g. `tailXXXX.ts.net`).
    * **DNS tab:** enable **MagicDNS** and **HTTPS Certificates** (required for the
      `*.ts.net` certificate).
    * **Access controls → Tags:** create a tag named `formae` (no `tag:` prefix),
      owned by yourself.
    * **Settings → Keys → Generate auth key:** **Reusable** on, **Ephemeral** off,
      **Tags** → `tag:formae`. Copy the `tskey-auth-...` value.

    <Steps>
      <Step title="Generate the API credential">
        Basic auth validates a bcrypt hash, produced locally:

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        gcp/scripts/gen-api-credential.sh
        ```

        Keep the printed **password** for the connect step; the **hash** goes to
        the apply. Also pick a stable Cloud SQL postgres password.
      </Step>

      <Step title="Apply">
        Cloud SQL spin-up is the long pole; `--watch` streams progress.

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        formae apply --mode reconcile gcp/bootstrap.pkl \
          --project <project> \
          --api-user formae --api-password-hash '<hash>' \
          --db-password '<db-password>' \
          --ts-authkey '<tskey-auth-...>' --ts-hostname formae-bootstrap \
          --watch
        ```

        The agent joins your tailnet as `<ts-hostname>.<your-tailnet>.ts.net` and
        serves the API over a trusted certificate on port `49684`. Confirm it joined
        in the Tailscale admin console under **Machines**.
      </Step>

      <Step title="Connect">
        Your CLI machine must be on the same tailnet (a device can be on only one
        at a time; use `tailscale switch` / `tailscale login` to reach it):

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        gcp/scripts/write-bootstrap-profile.sh --profile bootstrap \
          --fqdn formae-bootstrap.<your-tailnet>.ts.net --user formae --password '<password>'
        formae status agent --profile bootstrap
        ```

        That writes a [profile](/documentation/guides/manage-profiles) carrying
        the agent's URL so your CLI can reach it.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Public (HTTPS LB)">
    You also need a domain you own. The recommended certificate is a
    **Google-managed** one: pass `--domain` and Google provisions and auto-renews a
    publicly-trusted cert: no cert files, no renewal to manage.

    <Steps>
      <Step title="Generate the API credential">
        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        gcp/scripts/gen-api-credential.sh
        ```

        Keep the printed **password** for the connect step; the **hash** goes to
        the apply. Also pick a stable Cloud SQL postgres password.
      </Step>

      <Step title="Apply">
        Cloud SQL spin-up is the long pole; `--watch` streams progress.

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        formae apply --mode reconcile gcp/bootstrap.pkl --access public \
          --project <project> \
          --domain formae.example.com \
          --api-user formae --api-password-hash '<hash>' \
          --db-password '<db-password>' \
          --watch
        ```

        Instead of `--domain`, you can reference a pre-created cert with
        `--cert-name <selfLink>`, or bring your own PEM with
        `--cert-file ./fullchain.pem --key-file ./privkey.pem`. Pass exactly one of
        the three.
      </Step>

      <Step title="Point DNS at the load balancer">
        The stack reserves a global anycast address and prints it. Add an `A` record
        for `formae.example.com` pointing at it. A Google-managed cert stays
        `PROVISIONING` until DNS resolves and Google validates ownership (\~15 to 60 min),
        then goes `ACTIVE`. Verify:

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        curl https://formae.example.com/api/v1/health              # 200, auth-exempt
        curl -u formae:'<password>' https://formae.example.com/api/v1/agent  # 200
        ```
      </Step>

      <Step title="Connect">
        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        gcp/scripts/write-bootstrap-profile.sh --profile bootstrap \
          --fqdn formae.example.com --port 443 --user formae --password '<password>'
        formae status agent --profile bootstrap
        ```

        That writes a [profile](/documentation/guides/manage-profiles) carrying
        the agent's URL so your CLI can reach it.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Operate the agent

Day-2 procedures for a bootstrapped agent.

### Update

Upgrade the agent the same way you created it: re-apply `bootstrap.pkl` with a
newer image. `--formae-image` is the version knob; find a target tag on
[GitHub Releases](https://github.com/platform-engineering-labs/formae/releases).

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae apply --mode reconcile gcp/bootstrap.pkl <same flags as your apply> \
  --formae-image ghcr.io/platform-engineering-labs/formae:<new-version> \
  --watch
```

Pass the **same flags you applied with** (access mode, region, credentials) so the
reconcile changes only the image. Then match your local CLI to the new version:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae update <new-version>
```

To roll back, re-apply with the previous image tag.

<Warning>
  **Keep the install you bootstrapped from.** The local install you ran
  `bootstrap.pkl` from holds the agent's own infrastructure (the VPC, Cloud SQL
  database, and VM that *are* the agent) in its state. That state lives only there, so
  re-applying `bootstrap.pkl` to upgrade or change the agent depends on keeping that
  install and its datastore.
</Warning>

### Add extra plugins

The bootstrap image ships the standard plugin set (AWS, Azure, GCP, OCI, OVH, and
`auth-basic`). To manage anything else, build a derived image and pass it as
`--formae-image`. See
[Extend the agent image](/documentation/guides/extend-the-agent-image).

### Tune for production

The bootstrap installer favors a lean default. For production, choose a larger
`--size` as your inventory grows (see the sizing table below), and scope public
ingress to your load balancer with a Google-managed certificate rather than a
self-signed one.

### Tear down

Destroy the stack, then deregister the target:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae destroy --query "stack:formae-gcp-bootstrap"
formae apply --mode destroy gcp/destroy-target.pkl
```

<Note>
  You may need to run `destroy` twice. The first pass deletes the agent VM, but its
  Cloud SQL Auth Proxy connections take a moment to drain, so the `formae` database
  delete can fail with `pq: database "formae" is being accessed by other users`.
  Re-running `destroy` once the sessions have been reaped completes the teardown.
</Note>

## Reference

<Accordion title="Bootstrap flags and sizing">
  Flags for `formae apply ... gcp/bootstrap.pkl`:

  | Flag                         | Mode    | Notes                                                      |
  | ---------------------------- | ------- | ---------------------------------------------------------- |
  | `--project`                  | all     | GCP project ID (required)                                  |
  | `--access`                   | all     | `tailnet` (default) or `public`                            |
  | `--size`                     | all     | t-shirt size (see below); default `small`                  |
  | `--region` / `--zone`        | all     | location; default `us-central1` / `us-central1-a`          |
  | `--subnet-cidr`              | all     | private subnet range; default `10.100.1.0/24`              |
  | `--formae-image`             | all     | agent image reference, the version knob                    |
  | `--api-user`                 | all     | basic-auth username (default `formae`)                     |
  | `--api-password-hash`        | all     | bcrypt hash from `gen-api-credential.sh`                   |
  | `--db-password`              | all     | stable Cloud SQL postgres password                         |
  | `--name`                     | all     | resource name prefix (default `formae-bootstrap`)          |
  | `--domain`                   | public  | Google-managed cert hostname; also the DNS name for the LB |
  | `--cert-name`                | public  | full selfLink of a pre-created global `SslCertificate`     |
  | `--cert-file` + `--key-file` | public  | PEM chain + key; creates a `SELF_MANAGED` cert in-stack    |
  | `--ts-authkey`               | tailnet | reusable Tailscale auth key (`tag:formae`)                 |
  | `--ts-hostname`              | tailnet | tailnet (MagicDNS) hostname (defaults to `--name`)         |

  Passing a flag that belongs to another mode fails fast with a clear message.

  `--size` maps to a GCE machine type. Memory has a \~2 GB floor regardless of
  inventory size, because the agent image loads all its bundled resource plugins at
  once.

  | size     | VM machine type         | rough capacity          |
  | -------- | ----------------------- | ----------------------- |
  | `small`  | `e2-small` (2 GB)       | up to \~1,000 resources |
  | `medium` | `e2-medium` (4 GB)      | \~1,000 to 5,000        |
  | `large`  | `e2-standard-2` (8 GB)  | \~5,000 to 10,000       |
  | `xlarge` | `e2-standard-4` (16 GB) | \~10,000 to 20,000      |
</Accordion>

## See also

* [Manage profiles](/documentation/guides/manage-profiles): connect the CLI to the agent you just started, and switch between environments.
* [Configuration](/documentation/reference/configuration): agent and CLI settings, including authentication.
* [Architecture](/documentation/concepts/architecture): how the client, agent, and plugins fit together.
