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

# Target

> Where formae creates, manages, and discovers resources: a cloud account, region, or environment.

A target defines *where* resources live: the cloud account, region, or
environment formae creates and manages them in, and where it discovers existing
ones. Every forma needs at least one target.

<Warning>
  **One agent per target.** A target should only ever be managed by a single formae
  agent. Running multiple agents against the same target produces conflicting state
  and unpredictable behaviour. See [Architecture](/documentation/concepts/architecture).
</Warning>

## One target or many

If a forma defines exactly one target, every resource in it belongs to that
target automatically. You don't set a target on each resource:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
forma {
  new formae.Target {
    label = "aws-uw2"
    config = new aws.Config { region = "us-west-2" }
  }

  // No target set: this bucket uses the only target in the forma.
  new bucket.Bucket {
    label = "assets"
    bucketName = "my-assets"
  }
}
```

If a forma defines more than one target, the assignment is no longer implied.
Each resource must declare which target it belongs to:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
forma {
  local uw2 = new formae.Target {
    label = "aws-uw2"
    config = new aws.Config { region = "us-west-2" }
  }
  uw2

  local ue1 = new formae.Target {
    label = "aws-ue1"
    config = new aws.Config { region = "us-east-1" }
  }
  ue1

  new bucket.Bucket {
    label = "assets"
    bucketName = "my-assets"
    target = uw2.res
  }

  new bucket.Bucket {
    label = "certs"
    bucketName = "my-certs"
    target = ue1.res
  }
}
```

## Dynamic target config

Like a resource, a target's config fields don't have to be hardcoded. They can be
driven by a [property](/documentation/concepts/properties), for example a region
you set on the command line:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.Target {
  label = "aws"
  config = new aws.Config {
    region = properties.region   // set with --region on the CLI
  }
}
```

...or by a [resolvable](/documentation/concepts/resolvable#resolvables-in-targets),
which reads a value from another resource at apply time:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.Target {
  label = "grafana"
  config = new grafana.Config {
    url = lgtmStack.res.endpoints.at("lgtm:3000")   // resolved from a Compose stack
  }
}
```

See [Resolvables in targets](/documentation/concepts/resolvable#resolvables-in-targets)
for the full cross-plugin pattern.

## Discovery

Discovery scans your targets for resources formae doesn't manage yet. The
`discoverable` field controls whether a target is scanned; targets are
discoverable by default. Set `discoverable = false` on any target you want
excluded:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.Target {
  label = "staging-uw2"
  discoverable = false
  config = new aws.Config { region = "us-west-2" }
}
```

For the workflow of registering a target and pulling in what already runs on it,
see [Bring resources under management](/documentation/guides/bring-resources-under-management).

<Note>
  Applying a target-only forma (just targets, no resources) with `--mode reconcile`
  is safe. Reconcile only affects managed resources within a stack, and a
  target-only forma has no stack, so there is nothing to remove.
</Note>

## Unreachable targets

If the agent cannot reach a target for a sustained period, formae eventually
cleans up that target's discovered (unmanaged) resources from inventory so they
don't linger as stale entries. This is an inventory cleanup only: formae never
deletes anything in the cloud, never touches resources under formae management,
and never acts on a target with intermittent connectivity. Re-applying the
target brings its inventory back.

By default a target is cleaned up after it has been continuously unreachable for
24 hours. Set the `reap` field to change that window:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
new formae.Target {
  label = "batch-eu"
  config = new aws.Config { region = "eu-west-1" }
  reap = new formae.ReapAfter { maxUnreachable = 72.h }
}
```

To keep a target no matter how long it stays unreachable, never cleaning it up:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
  reap = new formae.NeverReap {}
```

## Replacing a target

Most target config changes update the target in place. Some config is immutable,
though: changing it (switching region, changing a connection endpoint) triggers a
**target replace**. formae deletes the target and every managed resource on it,
then recreates them against the new config.

Whether a resource survives that automatically depends on the resource:

* **Portable** resources can be recreated on the new target.
* **Non-portable** resources cannot. An EC2 instance, for example, may use a
  region-bound AMI that doesn't exist in the new region.

If a target replace would hit non-portable managed resources, formae **rejects the
operation** rather than break them:

```
Cannot replace target 'aws-uw2'

The following resources are bound to the current target configuration and
cannot be automatically moved to the new target:

  - my-stack/AWS::EC2::Instance/web

To proceed, manually remove these resources first with 'formae destroy',
then reapply to recreate them with the new target configuration.
```

Run `formae apply --simulate` first: it shows which config fields changed and
whether each change updates in place or forces a replace.

<Note>
  Which config fields update in place versus force a replace is decided by the
  plugin, not by you. As a user, it's enough to know that some target config updates
  in place and some forces a replace, and that `--simulate` tells you which. Plugin
  authors: see [Plugin development](/plugin-development).
</Note>

## Deleting a target

Destroy a target-only forma (just targets, no resources) to remove the declared
targets:

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae destroy target.pkl
```

Deleting a target also **cascade-deletes every managed resource on it** from the
cloud. Because that blast radius can be large, the CLI guards it: with `--yes`, a
destroy that would cascade is aborted unless you also pass
`--on-dependents=cascade`. Run interactively (without `--yes`) and formae shows
what will be cascaded and asks you to confirm.

<Info>
  Deleting a target removes its **unmanaged** resources from formae's inventory, but
  never from your cloud. formae only destroys resources it manages, so discovered
  resources you never brought under management stay exactly where they are; formae
  simply stops tracking them once their target is gone.
</Info>

### Targets during a full destroy

When you destroy a forma that has both targets and resources, formae keeps plain
targets (a cloud region target, say), because they exist independently of the
resources. Only a target whose config *depends on* a resource being destroyed,
through a resolvable reference, is deleted automatically. To remove a plain
target, destroy it separately with its own target-only forma.

## Sharing targets

Keep shared targets in one file and import them wherever you need them, so target
config stays consistent across your formae. This is how our own infrastructure is
organised:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
// vars.pkl
awsProd: formae.Target = new formae.Target {
  label = "aws-prod-uw2"
  config = new aws.Config { region = "us-west-2" }
}
```

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
import "./vars.pkl"

forma {
  vars.awsProd
  // resources on the shared target
}
```

## See also

* [Resolvable](/documentation/concepts/resolvable): resolving target config from another resource.
* [Discovery](/documentation/concepts/discovery): how targets enable discovery.
* [Stack](/documentation/concepts/stack): how resources are grouped for lifecycle.
* [Architecture](/documentation/concepts/architecture): one agent per target.
