> ## Documentation Index
> Fetch the complete documentation index at: https://checkly-422f444a-simo-red-1015-maintenance-window-timezone.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Monitor a shop with Terraform

> Declare a shop's whole monitoring stack in Terraform with the Checkly provider, from the alert channel and check group to Browser Checks loaded from spec files and an API check with body assertions.

export const CopyPromptButton = ({label = "Copy setup prompt", targetId = "ai-setup-prompt"}) => {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    try {
      const el = document.getElementById(targetId);
      const code = el?.querySelector("code");
      const text = code?.textContent || el?.textContent || "";
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      console.error("Failed to copy prompt:", err);
    }
  };
  return <button onClick={handleCopy} className="inline-flex items-center gap-2 px-5 py-3 rounded-lg font-semibold text-base
        border border-gray-200 dark:border-gray-700
        bg-white dark:bg-gray-800
        text-gray-800 dark:text-gray-200
        hover:bg-gray-50 dark:hover:bg-gray-700
        transition-colors cursor-pointer my-2">
      {copied ? <>
          <svg width="18" height="18" viewBox="0 0 16 16" fill="none">
            <path d="M13.3 4.3L6 11.6L2.7 8.3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          Copied!
        </> : <>
          <svg width="18" height="18" viewBox="0 0 16 16" fill="none">
            <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
            <path d="M11 5V3.5C11 2.67 10.33 2 9.5 2H3.5C2.67 2 2 2.67 2 3.5V9.5C2 10.33 2.67 11 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
          </svg>
          {label}
        </>}
    </button>;
};

By the end of this guide, your shop's monitoring lives in five Terraform resources: an email alert channel, a check group that owns locations, retries, and alerting, two Browser Checks loaded from Playwright spec files, and an API check that asserts on the response body.

```text Terminal theme={null}
  # checkly_alert_channel.shop_email will be created
  # checkly_check.books_api will be created
  # checkly_check.browser["home"] will be created
  # checkly_check.browser["search"] will be created
  # checkly_check_group_v2.shop will be created

Plan: 5 to add, 0 to change, 0 to destroy.
```

To follow along without your own app, clone the [sample project](https://github.com/checkly/docs/tree/main/samples/guides/terraform-shop). It monitors the [Danube demo shop](https://danube-web.shop) and its `/api/books` endpoint.

<Accordion title="Let your agent do it" icon="sparkles">
  To run this guide from your terminal or your coding agent, run `npx checkly init` in your project first. It installs the Checkly CLI and [Checkly Skills](/ai/skills) for your agent. Then paste the prompt below into Claude Code, Cursor, Codex, or any agent that supports skills. It builds the same setup as this guide, proves it with `npx checkly test --record`, and stops for your confirmation before `npx checkly deploy`.

  <div id="ai-setup-prompt">
    ```txt Prompt theme={null}
    Declare the monitoring for this shop in Terraform with the checkly/checkly provider, version "~> 1.0".

    Success criteria:
    1. `providers.tf` configures the provider from the variables `checkly_api_key` and `checkly_account_id`, read from TF_VAR_ environment variables. Never write credentials into a file.
    2. `alerts.tf` creates an email alert channel for failures and recoveries. Ask me for the address and pass it as a variable.
    3. `group.tf` creates a `checkly_check_group_v2` that enforces two locations, parallel runs, a linear retry strategy, and a run-based alert policy subscribed to the email channel. It sets a `SHOP_URL` environment variable from a `shop_url` variable.
    4. `scripts/` holds one Playwright spec per shop flow. Each spec reads `process.env.SHOP_URL` with the production URL as the fallback and asserts on visible content, not just that a page loaded. `browser.tf` creates one Browser Check per spec with `for_each` and `script = file(...)`.
    5. `api.tf` creates an API check for the product API that asserts the status code, the content type, and that the JSON body is not empty.
    6. Run `npx playwright test`, `terraform fmt -check`, `terraform validate`, and `terraform plan`, and show me the results.
    7. Stop. Wait for my confirmation before `terraform apply`.

    Explain each file you changed and why.
    ```
  </div>

  <CopyPromptButton />

  For this guide, the agent proves the setup with `npx playwright test` and `terraform plan`, and stops before `terraform apply`. The steps below are what it does, in the open.
</Accordion>

## Step 1: Configure the provider

Start in an empty folder. The provider needs a Checkly user API key and the ID of the account to create resources in. Declare both as variables so they never land in a file you commit.

```terraform providers.tf theme={null}
terraform {
  required_providers {
    checkly = {
      source  = "checkly/checkly"
      version = "~> 1.0"
    }
  }
}

variable "checkly_api_key" {
  type      = string
  sensitive = true
}

variable "checkly_account_id" {
  type = string
}

provider "checkly" {
  api_key    = var.checkly_api_key
  account_id = var.checkly_account_id
}
```

Terraform reads any variable from an environment variable prefixed with `TF_VAR_`. The [Terraform provider overview](/integrations/iac/terraform/overview) shows where to find both values in the app.

```bash Terminal theme={null}
export TF_VAR_checkly_api_key="<your user API key>"
export TF_VAR_checkly_account_id="<your account ID>"
terraform init
```

```text Terminal theme={null}
Initializing provider plugins...
- Finding checkly/checkly versions matching "~> 1.0"...
- Installing checkly/checkly v1.29.0...
- Installed checkly/checkly v1.29.0 (signed by a HashiCorp partner, key ID 4E5AC4D95E185A57)

Terraform has been successfully initialized!
```

## Step 2: Decide who hears about failures

A check that fails without telling anyone is a dashboard, not monitoring. Create the alert channel first, so everything after it can subscribe.

```terraform alerts.tf theme={null}
variable "alert_email" {
  type        = string
  description = "Where failure and recovery emails go."
}

resource "checkly_alert_channel" "shop_email" {
  email {
    address = var.alert_email
  }

  send_failure  = true
  send_recovery = true
  send_degraded = false
}
```

Set the address the same way as the credentials, with `export TF_VAR_alert_email="you@yourcompany.com"`. Email is the simplest channel to start with. The same resource takes Slack, PagerDuty, Opsgenie, SMS, and webhook blocks; see [alert channels for Terraform](/integrations/iac/terraform/alerting).

## Step 3: Put shared settings in one group

Every check for the shop should run from the same places, retry the same way, and alert the same people. Declare that once on a group instead of repeating it on every check.

```terraform group.tf theme={null}
variable "shop_url" {
  type    = string
  default = "https://danube-web.shop"
}

# Every check in this guide joins this group. The enforce blocks make the
# group, not each check, own locations, scheduling, retries, and alerting.
resource "checkly_check_group_v2" "shop" {
  name      = "Shop (Terraform)"
  activated = true
  tags      = ["terraform-shop"]

  enforce_locations {
    enabled   = true
    locations = ["us-east-1", "eu-west-1"]
  }

  enforce_scheduling_strategy {
    enabled      = true
    run_parallel = true
  }

  enforce_retry_strategy {
    enabled = true
    retry_strategy {
      type                 = "LINEAR"
      max_retries          = 2
      base_backoff_seconds = 30
      same_region          = false
    }
  }

  enforce_alert_settings {
    enabled = true

    alert_settings {
      escalation_type = "RUN_BASED"
      run_based_escalation {
        failed_run_threshold = 1
      }
    }

    alert_channel_subscription {
      channel_id = checkly_alert_channel.shop_email.id
      activated  = true
    }
  }

  environment_variable {
    key   = "SHOP_URL"
    value = var.shop_url
  }
}
```

`checkly_check_group_v2` changes nothing about its checks unless you ask it to. Each `enforce_` block you add takes that setting away from the checks and applies the group's value instead. Here the group runs every check from N. Virginia and Ireland in parallel, retries a failed run up to twice from a different region, and emails you as soon as one run still fails after its retries.

The `SHOP_URL` variable reaches every Browser Check in the group. Point the whole stack at a staging shop with `terraform plan -var="shop_url=<your staging URL>"`, without touching a check.

## Step 4: Monitor the shop's flows with Browser Checks

Keep each flow in its own Playwright spec file. The file is the check's script, and you can run it locally before Terraform ever sees it.

<CodeGroup>
  ```ts scripts/home.spec.ts theme={null}
  import { test, expect } from '@playwright/test'

  // SHOP_URL comes from the check group in Terraform. Locally, it falls
  // back to the production shop so the same file runs in both places.
  const shopUrl = process.env.SHOP_URL ?? 'https://danube-web.shop'

  test('home page lists the top sellers', async ({ page }) => {
    await page.goto(shopUrl)

    await expect(page.getByRole('heading', { name: 'Top sellers' })).toBeVisible()
    await expect(page.locator('.preview').first()).toBeVisible()
  })
  ```

  ```ts scripts/search.spec.ts theme={null}
  import { test, expect } from '@playwright/test'

  const shopUrl = process.env.SHOP_URL ?? 'https://danube-web.shop'

  test('search returns the matching books', async ({ page }) => {
    await page.goto(shopUrl)

    await page.locator('input[name="searchbar"]').fill('for')
    await page.getByRole('button', { name: 'Search' }).click()

    await expect(page.locator('.preview-title')).toHaveText([
      'The Foreigner',
      'The Transformation',
      'For Whom the Ball Tells',
      'Baiting for Robot',
    ])
  })
  ```
</CodeGroup>

The search test asserts the exact list of results, so a search that returns the wrong books fails as surely as one that returns nothing. Install Playwright and run both specs:

```bash Terminal theme={null}
npm install --save-dev @playwright/test
npx playwright install chromium
npx playwright test
```

```text Terminal theme={null}
Running 2 tests using 2 workers

  ✓  1 scripts/home.spec.ts:7:5 › home page lists the top sellers (626ms)
  ✓  2 scripts/search.spec.ts:5:5 › search returns the matching books (722ms)

  2 passed (1.3s)
```

Now turn every spec into a check. A `for_each` over a map creates one Browser Check per entry, and `file()` reads the spec as the check's script:

```terraform browser.tf theme={null}
# One Browser Check per spec file in scripts/. To monitor another flow,
# add a spec file and one line here.
locals {
  browser_flows = {
    home   = "Shop home page"
    search = "Shop search"
  }
}

resource "checkly_check" "browser" {
  for_each = local.browser_flows

  name      = each.value
  type      = "BROWSER"
  activated = true
  frequency = 10
  group_id  = checkly_check_group_v2.shop.id

  script = file("${path.module}/scripts/${each.key}.spec.ts")
}
```

The checks set only what is theirs: a name, a script, and how often to run. Everything else comes from the group. For the checkout itself, with test data that stays out of your sales numbers, follow [Monitor a checkout flow](/guides/monitoring-ecommerce-apps-using-playwright).

## Step 5: Assert on the product API

The storefront renders whatever `/api/books` returns. If the API answers 200 with an empty list, the pages load and show nothing to buy. An API check catches that every minute, without starting a browser.

```terraform api.tf theme={null}
resource "checkly_check" "books_api" {
  name      = "Shop books API"
  type      = "API"
  activated = true
  frequency = 1
  group_id  = checkly_check_group_v2.shop.id

  degraded_response_time = 1000
  max_response_time      = 3000

  request {
    url    = "${var.shop_url}/api/books"
    method = "GET"

    assertion {
      source     = "STATUS_CODE"
      comparison = "EQUALS"
      target     = "200"
    }

    assertion {
      source     = "HEADERS"
      property   = "content-type"
      comparison = "CONTAINS"
      target     = "application/json"
    }

    assertion {
      source     = "JSON_BODY"
      property   = "$.length"
      comparison = "GREATER_THAN"
      target     = "0"
    }
  }
}
```

Three assertions, three different failures: the API is down, it returns an HTML error page, or it returns an empty catalog. Responses slower than one second are degraded, and slower than three seconds fail.

## Step 6: Plan, then apply

Check the formatting and the configuration, then ask Terraform what it would create:

```bash Terminal theme={null}
terraform fmt -check
terraform validate
terraform plan
```

The plan lists all five resources. This part of the group block shows settings it enforces on its checks:

```text Terminal theme={null}
  # checkly_check_group_v2.shop will be created
  + resource "checkly_check_group_v2" "shop" {
      + activated   = true
      + name        = "Shop (Terraform)"

      + enforce_locations {
          + enabled   = true
          + locations = [
              + "eu-west-1",
              + "us-east-1",
            ]
        }

      + enforce_retry_strategy {
          + enabled = true

          + retry_strategy {
              + base_backoff_seconds = 30
              + max_duration_seconds = 600
              + max_retries          = 2
              + same_region          = false
              + type                 = "LINEAR"
            }
        }

      + environment_variable {
          + key    = "SHOP_URL"
          + value  = "https://danube-web.shop"
        }
    }

Plan: 5 to add, 0 to change, 0 to destroy.
```

When the plan matches what you expect, run `terraform apply` and type `yes` to create the checks in your account. From then on, the folder is the source of truth. Change a file, run `terraform plan` to see the difference, and apply.

<Warning>
  Manage these resources in Terraform or in the Checkly app, not both. Changing Terraform-managed resources in the app, and vice versa, is likely to cause issues.
</Warning>

## Verify it works

Break a check before it reaches production. In `scripts/search.spec.ts`, change `'Baiting for Robot'` to `'Waiting for Robot'` and run the specs again:

```bash Terminal theme={null}
npx playwright test
```

```text Terminal theme={null}
  ✓  2 scripts/home.spec.ts:7:5 › home page lists the top sellers (519ms)
  ✘  1 scripts/search.spec.ts:5:5 › search returns the matching books (5.6s)

  1) scripts/search.spec.ts:5:5 › search returns the matching books ────────────────────────────────

    Error: expect(locator).toHaveText(expected) failed

    Locator: locator('.preview-title')
    Timeout: 5000ms
    - Expected  - 1
    + Received  + 1

      Array [
        "The Foreigner",
        "The Transformation",
        "For Whom the Ball Tells",
    -   "Waiting for Robot",
    +   "Baiting for Robot",
      ]

  1 failed
    scripts/search.spec.ts:5:5 › search returns the matching books ─────────────────────────────────
  1 passed (6.1s)
```

The failure names the flow and the exact result that changed, and the home page check still passes. Nothing reached Checkly, because only `terraform apply` changes a check. Once the checks exist, the same edit shows up in `terraform plan` as a change to that check's `script`, so a reviewer sees the monitoring change next to the code change. Put `'Baiting for Robot'` back.

## Next

[Run checks on every deploy](/guides/sdlc-monitoring): `terraform apply` keeps your monitoring in step with your code. Run the checks against each deploy too, so a broken release fails before it reaches the schedule.

## Reference

* [Terraform provider overview](/integrations/iac/terraform/overview), [checks and groups](/integrations/iac/terraform/checks-groups), and [alert channels](/integrations/iac/terraform/alerting)
* [Testing Terraform scripts locally](/integrations/iac/terraform/testing-scripts-locally)
* [`checkly_check_group_v2` on the Terraform Registry](https://registry.terraform.io/providers/checkly/checkly/latest/docs/resources/check_group_v2)
* [Browser checks](/detect/synthetic-monitoring/browser-checks/overview) and [API checks](/detect/synthetic-monitoring/api-checks/overview)
* [Groups](/platform/groups)
* [Checkly CLI vs. Terraform and Pulumi](/cli/cli-vs-terraform-pulumi)
* [Checkly Skills](/ai/skills)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.