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

> Stand up a production formae agent on AWS 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 production 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, an ECS Fargate task, and a
PostgreSQL datastore) secure by default.

<Note>
  This guide covers the AWS bootstrap path. For other clouds, see
  [Install on Azure](/documentation/guides/install-agent-azure) and
  [Install on GCP](/documentation/guides/install-agent-gcp). Express and
  standard-ECS AWS installs exist for finer control and will be documented
  separately.
</Note>

## Choose an access mode

* **Public (ALB)**: a public HTTPS endpoint fronted by an Application Load
  Balancer using your ACM certificate. Requires a domain you own.
* **Tailscale (tailnet)**: private, reachable only over your Tailscale tailnet;
  the agent serves a trusted `*.ts.net` certificate. No public ingress.

Basic auth is on in both modes.

## Prerequisites

* The formae CLI, matching the agent image version. If you have not installed it,
  see the [Quick start](/documentation/get-started/quickstart).
* AWS credentials for the target account.
* `openssl` and `htpasswd` (from `apache2-utils` / `httpd-tools`), or `docker`, to
  generate the basic-auth credential.

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="Public (ALB)">
    You also need a domain you own and an ACM certificate for it, status
    `ISSUED`, in the same region as the ALB. Note the certificate ARN.

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

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

        Keep the printed **password** for the connect step; the **hash** goes to
        the apply.
      </Step>

      <Step title="Apply">
        RDS spin-up is the long pole (\~10 min); on a terminal the apply shows live progress.

        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        formae apply --mode reconcile aws/bootstrap.pkl \
          --access alb --region <region> \
          --cert-arn <acm-arn> --domain agent.example.com \
          --api-user formae --api-password-hash '<hash>'
        ```

        To restrict who can reach the ALB, add `--allow-cidr <cidr>` (open by
        default).
      </Step>

      <Step title="Point DNS at the ALB">
        Add `agent.example.com` pointing at the ALB's DNS name (a Route53 alias
        record, or a DNS-only CNAME elsewhere). Connect via `https://<your-domain>`,
        not the raw `*.elb.amazonaws.com` name: the certificate only matches your
        domain.
      </Step>

      <Step title="Connect">
        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        aws/scripts/write-bootstrap-profile.sh --profile bootstrap \
          --domain agent.example.com --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="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">
        ```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
        aws/scripts/gen-api-credential.sh
        ```

        Keep the printed **password** for the connect step; the **hash** goes to
        the apply.
      </Step>

      <Step title="Apply">
        RDS spin-up is the long pole (\~10 min); on a terminal the apply shows live progress.

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

        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"]}}
        aws/scripts/write-bootstrap-profile.sh --profile bootstrap --access tailnet \
          --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>
</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 aws/bootstrap.pkl <same flags as your apply> \
  --formae-image ghcr.io/platform-engineering-labs/formae:<new-version>
```

Pass the **same flags you applied with** (access mode, region, credentials, and
the `alb`/`tailnet` options) so the reconcile changes only the image: formae plans
a new task definition and a rolling service update. 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, database, and
  ECS service 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).

### Rotate Aurora credentials

If you use Aurora as the datastore, its managed master-credential rotation (the
default 30-day cycle) updates the cluster password but not the agent's
`formae-config` secret that holds the connection string, so the agent fails to
connect after the next rotation. The simplest mitigation is to disable managed
rotation and rotate on your own schedule:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
aws rds modify-db-cluster \
  --db-cluster-identifier formae-db \
  --manage-master-user-password false \
  --apply-immediately
```

Address this before the first 30-day mark.

### Back up state

The agent is stateless; everything recoverable lives in the datastore. Aurora's
default automated backups (1-day retention with point-in-time recovery) cover the
formae state. For longer retention, raise the cluster's `--backup-retention-period`
or run scheduled `aws rds create-db-cluster-snapshot` jobs.

### Shell into the agent

The service runs with ECS Exec enabled, so you can open a shell inside the running
Fargate container (requires the AWS Session Manager plugin locally):

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
aws ecs execute-command --region <region> \
  --cluster <cluster-name> \
  --task "$(aws ecs list-tasks --region <region> --cluster <cluster-name> --query 'taskArns[0]' --output text)" \
  --container formae-agent --interactive --command /bin/sh
```

### Tune for production

The bootstrap installer favors a lean default. For production, edit
`aws/bootstrap.pkl` / `aws/vars.pkl`:

* **Multi-AZ database:** the RDS instance is single-AZ by default. Set `multiAZ = true` for automatic failover (roughly doubles RDS cost and deploy time).
* **Storage:** `maxAllocatedStorage` caps storage autoscaling at 100 GB; raise it for larger inventories.
* **Ingress (`alb` mode):** scope the ALB with `--allow-cidr`, or use `tailnet` mode for no public surface at all.
* **Sizing:** choose a larger `--size` as your inventory grows (see the sizing table below).

### Tear down

Destroy in two steps; `destroy` removes the resources but not the target
registration:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae destroy --yes aws/bootstrap.pkl <same flags as your apply>
formae destroy --yes aws/destroy-target.pkl
```

## Reference

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

  | Flag                  | Mode    | Notes                                                                                                                               |
  | --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
  | `--access`            | both    | `alb` (default) or `tailnet`                                                                                                        |
  | `--region`            | both    | region for the apply                                                                                                                |
  | `--size`              | both    | Fargate t-shirt size (see below); default `small`                                                                                   |
  | `--database`          | both    | `new` (default) provisions RDS; `existing` uses your DB (with `--db-host` / `--db-user` / `--db-name` / `--db-password-secret-arn`) |
  | `--formae-image`      | both    | agent image reference, the version knob                                                                                             |
  | `--api-user`          | both    | basic-auth username (default `formae`)                                                                                              |
  | `--api-password-hash` | both    | bcrypt hash from `gen-api-credential.sh`                                                                                            |
  | `--cert-arn`          | alb     | ACM certificate ARN (same region)                                                                                                   |
  | `--domain`            | alb     | hostname clients connect to; must match the certificate                                                                             |
  | `--allow-cidr`        | alb     | restrict ALB ingress to one CIDR (open by default)                                                                                  |
  | `--ts-authkey`        | tailnet | reusable Tailscale auth key                                                                                                         |
  | `--ts-hostname`       | tailnet | tailnet (MagicDNS) hostname (defaults to the stack name)                                                                            |

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

  `--size` maps to a Fargate-valid CPU/memory pair. Memory has a \~2 GB floor
  regardless of inventory size, because the agent image loads all its bundled
  resource plugins at once.

  | size     | vCPU / memory | rough capacity          |
  | -------- | ------------- | ----------------------- |
  | `small`  | 0.5 / 2 GB    | up to \~1,000 resources |
  | `medium` | 1 / 2 GB      | \~1,000 to 5,000        |
  | `large`  | 2 / 4 GB      | \~5,000 to 10,000       |
  | `xlarge` | 4 / 8 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.
