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

# TTL policy

> Automatically destroy a stack and its resources after a set duration.

A TTL (time-to-live) policy destroys a stack and everything in it once its deadline passes. It's the mechanism for ephemeral infrastructure: dev workspaces, test environments, demos, anything that should clean itself up without someone remembering to run `destroy`.

## How it works

The deadline is expressed one of two ways: a **duration** (`ttl`), measured from when the stack is created, or an **absolute instant** (`expiresAt`). Either way it counts down in the background: no `apply` is needed to trigger the eventual destroy. Re-applying the stack does not move the deadline; a `ttl` always counts from the stack's creation, and an `expiresAt` names the instant directly.

When the deadline passes, formae destroys the stack and all its resources.

## Configuration

A TTL policy carries exactly one of `ttl` or `expiresAt`. Declaring both, or neither, fails validation when the forma is evaluated.

| Property       | Type                     | Description                                                                                                                              |
| -------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `ttl`          | Duration                 | How long the stack should live, measured from its creation, for example `1.h`, `24.h`, `7.d`                                             |
| `expiresAt`    | String                   | The instant to destroy the stack at, as an RFC 3339 UTC timestamp, for example `"2026-09-01T00:00:00Z"`. Sub-second precision is dropped |
| `onDependents` | `"abort"` \| `"cascade"` | What to do if resources outside the stack depend on it. Defaults to `"abort"`                                                            |

### Relative or absolute?

* **`ttl`** fits infrastructure whose lifetime is known at creation: a sandbox that lives for the workday, a test environment that should survive its CI run and no longer. Because it measures from stack creation, attaching a `ttl` to a stack that already exists counts the time the stack has already lived: a `ttl = 1.h` on a three-day-old stack is already expired and the stack is destroyed on the next expiry check.
* **`expiresAt`** fits deadlines known as a date: the end of a trial, a scheduled teardown, a booking window. It reads the same to every observer, never shifts on re-apply, and is the right form when another system computes the deadline. To destroy an existing stack some time from now, compute the instant and set `expiresAt` rather than attaching a `ttl`.

To move a deadline, re-apply the stack with a new `expiresAt` (or a new `ttl`; the change applies in place, still anchored to creation). To remove one, re-apply the stack in reconcile mode without the policy.

### onDependents

When the TTL expires, formae checks whether any resource outside the stack depends on a resource inside it:

* **`abort`** (default): cancel the destruction. The stack stays intact until the dependency is removed.
* **`cascade`**: delete the dependent resources too, following the dependency chain into other stacks.

<Warning>
  `cascade` can delete resources in stacks you didn't intend to touch, anywhere the dependency chain leads. Use `abort` unless you're certain nothing outside the stack should survive it.
</Warning>

## Examples

**Inline TTL policy:**

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
amends "@formae/forma.pkl"

import "@formae/formae.pkl"
import "@aws/aws.pkl"
import "@aws/logs/loggroup.pkl"

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

forma {
  devTarget

  new formae.Stack {
    label = "feature-xyz"
    description = "Temporary dev environment for feature XYZ"
    policies = new Listing {
      new formae.TTLPolicy {
        ttl = 8.h              // Destroy after 8 hours
        onDependents = "abort" // Don't destroy if other resources depend on it
      }
    }
  }

  new loggroup.LogGroup {
    label = "feature-logs"
    stack = "feature-xyz"
    target = devTarget.res
    logGroupName = "/dev/feature-xyz/logs"
    retentionInDays = 1
  }
}
```

**Absolute deadline**, for a stack that must be gone at a known instant:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
  new formae.Stack {
    label = "pilot-workshop"
    description = "Workshop environment for the September pilot"
    policies = new Listing {
      new formae.TTLPolicy {
        expiresAt = "2026-09-01T00:00:00Z" // Destroy at this instant
        onDependents = "abort"
      }
    }
  }
```

**Reusable TTL policy**, shared by every developer's sandbox stack:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
  new formae.Stack {
    label = "pilot-workshop"
    description = "Workshop environment for the September pilot"
    policies = new Listing {
      new formae.TTLPolicy {
        expiresAt = "2026-09-01T00:00:00Z" // Destroy at this instant
        onDependents = "abort"
      }
    }
  }
```

**Reusable TTL policy**, shared by every developer's sandbox stack:

```pkl theme={"languages":{"custom":["/languages/pkl.json"]}}
amends "@formae/forma.pkl"

import "@formae/formae.pkl"
import "@aws/aws.pkl"
import "@aws/logs/loggroup.pkl"

local ephemeralPolicy = new formae.TTLPolicy {
  label = "ephemeral-4h"
  ttl = 4.h
  onDependents = "cascade"
}

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

forma {
  ephemeralPolicy
  devTarget

  new formae.Stack {
    label = "alice-dev"
    description = "Alice's development environment"
    policies = new Listing { ephemeralPolicy.res }
  }

  new loggroup.LogGroup {
    label = "alice-logs"
    stack = "alice-dev"
    target = devTarget.res
    logGroupName = "/dev/alice/logs"
  }

  new formae.Stack {
    label = "bob-dev"
    description = "Bob's development environment"
    policies = new Listing { ephemeralPolicy.res }
  }

  new loggroup.LogGroup {
    label = "bob-logs"
    stack = "bob-dev"
    target = devTarget.res
    logGroupName = "/dev/bob/logs"
  }
}
```

## Use cases

* **Development environments** that clean themselves up at the end of the workday.
* **CI/CD test infrastructure** that disappears once the test run finishes with it.
* **Demo environments** that auto-destroy after the demo window.
* **Cost control**: forgotten resources stop accumulating cost on their own.

## Monitoring

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

Shows every stack's attached policies, including TTL and its expiry.

## See also

* [Policy](/documentation/concepts/policy): inline vs. reusable policies, and how to query them.
* [Auto-reconcile policy](/documentation/concepts/policies/auto-reconcile): enforce declared state instead of expiring it. The two can be combined on the same stack.
