> For the complete documentation index, see [llms.txt](https://docs.firefly.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firefly.ai/detailed-guides/workflows/local-execution.md).

# Local Execution (State-Only Workspaces)

Firefly can act as the **remote state backend** for your Terraform projects. You point Terraform at Firefly with a standard `cloud {}` block, and Firefly stores your state, tracks every version, and manages state locking — while your `terraform plan` and `terraform apply` continue to run locally on your own machine or CI runner.

No version-control connection is required. This is often called a **state-only** or **VCS-less** workspace: Firefly creates it for you automatically the first time you run `terraform init`, and it appears in the Firefly console right away — before you have applied anything.

> **Choosing a state backend mode:** This page covers **local execution**, where Firefly stores your state but you run Terraform yourself and the workspace is created on demand. If instead you want Firefly (or a self-hosted runner pool) to *run* `plan` and `apply` for you against a pre-created workspace, see [Remote State Management](/detailed-guides/workflows/remote-state-management.md). The two modes use different backend blocks (`cloud {}` here vs. `backend "remote"` there) — pick the one that matches how you want execution handled.

***

## Overview

When you use Firefly as your backend:

* **Your state lives in Firefly.** Every `apply` uploads a new, versioned copy of your Terraform state. You can browse state versions and serials in the console.
* **Execution stays local.** Firefly does not run Terraform for you in this mode. `plan` and `apply` execute wherever you run them; only the resulting state is sent to Firefly. The workspace's execution mode is therefore `local`.
* **Locking is handled for you.** Concurrent `apply`s are serialized through Firefly's state lock, exactly as with any Terraform remote backend.
* **The workspace is created on demand.** You do not pre-create anything in the UI. The first `terraform init` that references a new workspace name creates it.

### When to use this

Use a state-only workspace when you want Firefly to be the source of truth for your Terraform state and to give you visibility into that state, but you want to keep driving Terraform yourself — from a laptop, a script, or an existing CI pipeline — rather than connecting a Git repository.

***

## Prerequisites

* **Terraform** installed (`terraform version`).
* A **Firefly account**, and your **account ID** — a 24-character hexadecimal string. This is the value you will use for `organization`. It is **not** your company or account display name.
* **`curl`** and **`jq`** (or any HTTP client) to exchange your access key for a token in Step 2.

Throughout this guide the Firefly API host is **`api.firefly.ai`**. Use that host for both the token exchange and the `cloud {}` block.

***

## Step 1 — Create an access key in the console

Access keys are how you authenticate to the Firefly API from outside the browser.

1. In the Firefly console, go to **Settings → Access Management**.
2. Under the **API Keys** section, click **New API Key** to generate a new **access key** and **secret key** pair.
3. **Copy the secret key immediately** — it is shown only once and cannot be retrieved later. Store it somewhere safe (a password manager or secrets vault).

You now have two values:

* an **access key** (an identifier), and
* a **secret key** (the credential).

> For more on managing keys, roles, and permissions, see [Access Management & RBAC](/getting-started/access-management-rbac.md).

***

## Step 2 — Exchange the access key for a bearer token

Terraform authenticates to Firefly with a **bearer token (a JWT)**, not with the access key directly. You mint that token by calling the login endpoint with your key pair.

> There is **no `terraform login`** flow for Firefly. Do not run `terraform login` — mint the token yourself as shown here.

```bash
TOKEN=$(curl -s -X POST "https://api.firefly.ai/v2/login" \
  -H 'Content-Type: application/json' \
  -d '{
        "accessKey": "YOUR_ACCESS_KEY",
        "secretKey": "YOUR_SECRET_KEY",
        "duration":  "1y"
      }' | jq -r .accessToken)
```

The token is returned in the **`accessToken`** field (note the camelCase). The snippet above extracts it into a `TOKEN` shell variable.

You can check for a successful response by echoing the `TOKEN` variable:

```bash
echo $TOKEN
```

### The `duration` attribute

`duration` is **optional** and controls how long the minted token remains valid.

* **Format:** `<number><unit>`, where the unit is one of `h` (hours), `d` (days), `w` (weeks), `m` (months), or `y` (years). Examples: `1h`, `12h`, `7d`, `2w`, `6m`, `1y`.
* **Default:** `24h` if you omit the field.
* **Maximum:** `1y`. A request for `"duration": "1y"` returns a token valid for a full 365 days; omitting the field yields the 24-hour default.

Choose the **shortest** duration that fits your rotation cadence. A short-lived token limits your exposure if it is ever leaked — see [Security and token lifecycle](#security-and-token-lifecycle) below. Long-lived tokens (up to a year) are convenient for stable CI systems but should be stored in a secrets manager and rotated deliberately.

***

## Step 3 — Store the token in a Terraform credentials file

Terraform reads a **per-host** token from a JSON credentials file. Create one and store the token **raw** — do not prefix it with `Bearer`; Terraform adds that itself when it calls the API.

```bash
cat > credentials.tfrc.json <<EOF
{
  "credentials": {
    "api.firefly.ai": {
      "token": "$TOKEN"
    }
  }
}
EOF
```

***

## Step 4 — Tell Terraform where the credentials file is

Terraform does **not** automatically look for a `credentials.tfrc.json` in your project directory. A file of that name only has meaning at Terraform's well-known credentials location. Choose one of the following.

### Option A — Point Terraform at the local file (recommended)

Set `TF_CLI_CONFIG_FILE` to the file you created in Step 3. This keeps the token scoped to the project directory and is ideal for CI pipelines and ephemeral environments:

```bash
export TF_CLI_CONFIG_FILE="$PWD/credentials.tfrc.json"
```

Set it once per shell session; every subsequent `terraform` command in that shell will find the token.

### Option B — Install it globally

Place the credentials at Terraform's default location so that a bare `terraform init` works from any directory, with no environment variable:

```
~/.terraform.d/credentials.tfrc.json
```

The file uses the exact same JSON structure as in Step 3:

```json
{
  "credentials": {
    "api.firefly.ai": {
      "token": "YOUR_TOKEN"
    }
  }
}
```

Remember to `chmod 600 ~/.terraform.d/credentials.tfrc.json`. If you already have other hosts in that file, add `api.firefly.ai` alongside them rather than overwriting it.

### Option C — Environment variable

Alternatively, skip the file entirely and provide the token through a host-specific environment variable. Terraform maps the host into the variable name by replacing dots with underscores:

```bash
export TF_TOKEN_api_firefly_ai="$TOKEN"
```

This is convenient in CI, though the credentials file (Option A) is generally less error-prone.

***

## Step 5 — Configure the `cloud {}` block

In your root module (for example `main.tf`), add a `cloud {}` block inside `terraform {}`:

```hcl
terraform {
  cloud {
    hostname     = "api.firefly.ai"             # Firefly API host
    organization = "FIREFLY_ACCOUNT_ID"       # your Firefly account ID
    workspaces {
      name = "WORKSPACE_NAME"                 # the name shown in the console
    }
  }
}

# ...your resources below...
```

* **`hostname`** is always `api.firefly.ai`.
* **`organization`** is your 24-character Firefly **account ID** — not a display name.
* **`workspaces.name`** is the exact name that will appear in the Firefly console.

> **Note:** assigning a new workspace to a specific project through the `cloud {}` block is not currently supported. Workspaces created this way are placed in your account's default (root) project. Do not set a `project` attribute inside `workspaces {}`.

***

## Step 6 — Initialize, plan, and apply

```bash
terraform init
```

On the first `init`, Terraform contacts Firefly, authenticates with your token, and — if the named workspace does not yet exist — Firefly **creates it automatically** as a state-only workspace (execution mode `local`, no VCS repository attached). The workspace appears in the console immediately, before any apply.

```bash
terraform plan     # reads your remote state, computes the diff locally
terraform apply    # acquires the state lock, uploads a new state version, releases the lock
```

`plan` and `apply` run entirely on your machine. On a successful `apply`, Terraform uploads the resulting state to Firefly and increments the workspace's state version (its serial number).

If `init` fails with **`Required token could not be found`**, Terraform is not seeing your credentials — revisit Step 4. (Do not run `terraform login`; it does not apply to Firefly.)

***

## Step 7 — View the workspace in the console

Open the Firefly console and go to **Workflows → Workspaces**. Your workspace — named exactly as you set it in Step 5 — is listed. Open it to see its current state version, serial, and last-updated time.

***

## Managing and removing a workspace

To tear down the infrastructure and remove the workspace:

```bash
terraform destroy
```

Then delete the workspace itself, either from the UI or via the API. Deletion is **by workspace ID**:

```bash
curl -X DELETE \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.firefly.ai/v2/workspaces/<WORKSPACE_ID>"
```

You can find the workspace ID by resolving the workspace by name through the API.

***

## Security and token lifecycle

Treat the minted token like any long-lived secret:

* **Deleting an access key does not revoke tokens already minted from it.** A token is validated by its signature and expiry; the access key is consulted only when a *new* token is minted, never on a per-request basis. An already-issued token therefore remains valid until it expires, even after you delete the key it came from.
* **Prefer short durations and rotate.** Minting a fresh token is cheap. Favor short-lived tokens over a single long-lived one wherever your workflow allows.
* **Store tokens in a secrets manager**, never in source control, and restrict file permissions (`chmod 600`) on any credentials file.
* **If a token leaks, deleting the key is not sufficient** — because existing tokens stay valid until expiry, a leaked long-lived token is exposed for its full lifetime. This is the strongest reason to keep durations short.

***

## Troubleshooting

| Symptom                                       | Likely cause                                                   | Fix                                                                                                                   |
| --------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `Required token could not be found` on `init` | Terraform can't find the credentials                           | Apply Step 4 (`TF_CLI_CONFIG_FILE`, the global file, or `TF_TOKEN_api_firefly_ai`). Do **not** run `terraform login`. |
| `unauthorized` / HTTP 401 on `init`           | Token expired or wrong key pair                                | Re-mint the token (Step 2) and refresh your credentials file                                                          |
| Workspace does not appear in the console      | `organization` set to a display name instead of the account ID | Use your 24-character account ID                                                                                      |
| `init` cannot reach the host                  | Wrong or unreachable `hostname`                                | Confirm the host is `api.firefly.ai` and reachable from your network                                                  |
| Terraform errors when a `project` is set      | Project assignment via `cloud {}` is not supported             | Remove the `project` attribute from `workspaces {}`                                                                   |

***

## Quick reference

| Item                      | Value                                                           |
| ------------------------- | --------------------------------------------------------------- |
| API host                  | `api.firefly.ai`                                                |
| Token endpoint            | `POST https://api.firefly.ai/v2/login`                          |
| Login request body        | `{ "accessKey", "secretKey", "duration" }`                      |
| Login response fields     | `accessToken`, `expiresAt`, `tokenType`                         |
| `duration` format         | `<number><unit>` — `h`/`d`/`w`/`m`/`y`; default `24h`; max `1y` |
| Credentials file (local)  | pointed to by `TF_CLI_CONFIG_FILE`                              |
| Credentials file (global) | `~/.terraform.d/credentials.tfrc.json`                          |
| Env-var alternative       | `TF_TOKEN_api_firefly_ai`                                       |
| Delete a workspace        | `DELETE https://api.firefly.ai/v2/workspaces/<WORKSPACE_ID>`    |

***

## Related guides

* [Remote State Management](/detailed-guides/workflows/remote-state-management.md): Use Firefly as your backend with Firefly-managed or self-hosted runner execution (managed mode).
* [Firefly Workflows](/detailed-guides/workflows.md): High-level concepts, projects, and execution options.
* [Authentication](/general-information/api/auth.md): Creating API key pairs and minting tokens.
* [Access Management & RBAC](/getting-started/access-management-rbac.md): Managing keys, roles, and permissions.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.firefly.ai/detailed-guides/workflows/local-execution.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
