TERRAFORM PROVIDER · v0.1.0 · FREE FOR INDIVIDUALS

The official Terraform provider for LastPing.

LastPing has an official Terraform provider, published on the public Terraform Registry as lastping-dev/lastping. Declare your cron, CI/CD and AI-agent monitors as code, version them alongside the jobs they watch, import the ones you already have, and let an AI agent generate them. Six resources, one ephemeral resource, six data sources. Free for individuals, with no monitor caps.

Install — three steps

A normal registry provider. Nothing to vendor, nothing to build. Terraform 1.5 or later; 1.10 or later if you want the ephemeral API key.

Declare the provider

Leave api_key out of the provider block on purpose: the provider reads LASTPING_API_KEY from the environment, so the credential never sits in a file you might commit.

terraform { required_providers { lastping = { source = "lastping-dev/lastping" version = "~> 0.1" } } } provider "lastping" { # Set LASTPING_API_KEY in your environment instead of hardcoding it. }

Authenticate

Create a key at app.lastping.dev → Settings → API Keys. The provider is scoped to the project that key belongs to — one key, one project, no ambiguity about what a plan is about to change.

export LASTPING_API_KEY="lp_…" # Self-hosting? Point it somewhere else: export LASTPING_ENDPOINT="https://lastping.internal.example.com"

Initialise and apply

terraform init downloads the provider from the public registry and verifies its signature. From there it is an ordinary Terraform workflow — plan, review, apply, and the monitoring exists.

$ terraform init - Finding lastping-dev/lastping versions matching "~> 0.1"... - Installing lastping-dev/lastping v0.1.0... - Installed lastping-dev/lastping v0.1.0 (self-signed, key ID E863B1802B5C1E64) Terraform has been successfully initialized! $ terraform apply

A complete monitor, end to end

Monitor, destination, routing and alert wording — the whole chain in one file. This is not a fragment: it applies as-is against the real API.

# The Slack incoming-webhook URL is itself the credential. Keep it out of the # repo: pass it with TF_VAR_slack_webhook_url or a secrets manager. variable "slack_webhook_url" { type = string sensitive = true } # 1 — the monitor. It expects a ping every night at 03:00 UTC and waits # grace_s seconds past that before opening an incident. grace_s is required # and validated to [60, 31536000]. resource "lastping_monitor" "nightly_backup" { name = "Nightly backup" slug = "nightly-backup" schedule_kind = "cron" cron_expr = "0 3 * * *" tz = "UTC" grace_s = 1800 tags = ["env:prod", "team:platform"] } # 2 — where alerts go. resource "lastping_destination" "oncall_slack" { kind = "slack" name = "#oncall" webhook_url = var.slack_webhook_url } # 3 — which events go there. One resource per (monitor, event type). resource "lastping_route" "backup_down" { monitor_id = lastping_monitor.nightly_backup.id event_type = "down" destination_ids = [lastping_destination.oncall_slack.id] } resource "lastping_route" "backup_recovery" { monitor_id = lastping_monitor.nightly_backup.id event_type = "recovery" destination_ids = [lastping_destination.oncall_slack.id] } # 4 — what the alert says. Placeholders are {token}, not Go templates. resource "lastping_alert_template" "nightly_backup" { monitor_id = lastping_monitor.nightly_backup.id templates = { "down" = "{check_name} has not checked in. Last ping {last_ping}, expected {schedule}. {incident_url}" "recovery" = "{check_name} recovered." } }

terraform apply creates five resources. The monitor's ping URL comes back as ping_url — feed it to whatever runs the job, and the loop is closed.


Export as Terraform — adopt, don't recreate

The usual reason teams never move monitoring into code is that they already have monitors, and rewriting them by hand is a day nobody has. So LastPing writes the HCL for you — and pairs every resource with an import block, so the first plan adopts what exists instead of proposing to replace it.

Export the project

From the dashboard, or straight from the API with the same key Terraform uses:

$ curl -fsS -H "Authorization: Bearer $LASTPING_API_KEY" \ https://app.lastping.dev/api/v1/export/terraform > lastping.tf

Read what came out

Real output, unedited. Each import block tells Terraform the resource already exists and gives it the id to adopt — a monitor by its slug, a destination by UUID, a route by <monitor-id>:<event-type>.

# Generated by LastPing. # Secrets are not exported — the API never returns them. Every `var.` # reference below has a matching variable block you must fill in. # Import blocks adopt your existing resources instead of recreating them. import { to = lastping_monitor.nightly_backup id = "nightly-backup" } resource "lastping_monitor" "nightly_backup" { name = "Nightly backup" slug = "nightly-backup" schedule_kind = "cron" cron_expr = "0 3 * * *" tz = "UTC" grace_s = 1800 tags = ["env:prod", "team:platform"] } import { to = lastping_destination.oncall id = "29145b3c-64c7-4b7f-91b7-a09c0eecaee1" } resource "lastping_destination" "oncall" { kind = "slack" name = "#oncall" webhook_url = var.slack_webhook_url_oncall } import { to = lastping_route.nightly_backup_down id = "abbb8a64-dc85-4cbb-bf22-af13bfaf64bc:down" } resource "lastping_route" "nightly_backup_down" { monitor_id = lastping_monitor.nightly_backup.id event_type = "down" destination_ids = [lastping_destination.oncall.id] } variable "slack_webhook_url_oncall" { # TODO: set this — not exported, the API never returns secrets. type = string sensitive = true description = "webhook_url for destination \"#oncall\"" }

Fill in the secrets, then plan

The export cannot include credentials — the API never returns them — so every secret attribute comes out as a var. reference with a matching sensitive = true variable block and a loud TODO. That is deliberate: an empty string would silently ship a destination that delivers nothing. Set the variables, then plan.

$ terraform plan Plan: 0 to add, 0 to change, 0 to destroy.

A clean plan is the whole point: your monitoring is now in version control and nothing about it changed.


The agent writes the monitoring in the same pull request

When a human builds a scheduled job, someone eventually files the "add monitoring" ticket. When an agent builds it, nobody does — the job ships and nothing watches it. The fix is not a better ticket process: it is making the monitoring part of the diff.

  • The provider's schema is public, so an agent can write correct HCL Every resource, attribute and validation rule is documented on the Terraform Registry. An agent adding a nightly job to a repo can add the lastping_monitor that watches it in the same commit — and the reviewer sees both changes side by side, in one diff, instead of discovering six months later that the job has never been monitored.
  • CI applies it, so the monitor cannot drift from the job The same pipeline that deploys the job runs terraform apply. Rename the job and you rename its monitor; delete the job and the monitor goes with it. There is no state in which a monitor sits green because the thing it was watching stopped existing.
  • Agents that would rather act than write files use MCP Terraform is the right tool when monitoring belongs in a reviewed, versioned repo. When an agent needs to create a monitor for itself right now — mid-conversation, with no PR — the LastPing MCP server exposes the same capabilities as tool calls. Both paths reach the same API and the same monitors; Export as Terraform is how a monitor created over MCP later becomes code.

The concept, at length, on its own page: what monitoring as code means and why it matters more with agents.


A credential that does not outlive the run

The uncomfortable question about any provider is where its key ends up. The ephemeral lastping_api_key has a good answer: nowhere.

# Mints a key that exists only for this run and is revoked when the run ends. # Nothing about it is written to plan or state. Requires Terraform 1.10+. ephemeral "lastping_api_key" "run" { name = "terraform-run" ttl = "30m" } provider "lastping" { alias = "run" api_key = ephemeral.lastping_api_key.run.key } resource "lastping_monitor" "nightly_backup" { provider = lastping.run name = "Nightly backup" slug = "nightly-backup" schedule_kind = "cron" cron_expr = "0 3 * * *" tz = "UTC" grace_s = 1800 }

Terraform refuses to place an ephemeral value anywhere that persists, so the key cannot end up in state, in an output, or in a resource argument even by accident. The ttl is the safety net: if a run is killed before it can revoke its key, the key still expires on its own server-side. Set it to comfortably exceed your longest apply — LastPing has no endpoint to extend a key, so one cannot be renewed in place.

The managed lastping_api_key resource still exists for credentials that must outlive the run — a CI job, a container. Its plaintext lands in state, which the provider documentation says plainly rather than burying.


Resource reference

Six managed resources, one ephemeral resource, six data sources. Every row links to its page on the Terraform Registry.

LastPing Terraform provider — resources, ephemeral resources and data sources.
Resource What it manages
lastping_monitor A heartbeat, CI or HTTP probe monitor. Cron or simple-interval schedule, tags, runaway ceiling, pause state. Creating a monitor whose slug already exists fails loudly rather than taking it over silently — import it instead.
lastping_destination Where alerts are delivered: Slack, email, custom webhook, Telegram, Discord, ntfy, Pushover, Microsoft Teams, Google Chat. Credentials are write-only — stored, never returned.
lastping_route One monitor's alerts for one event type (down, recovery, fail) routed to a list of destinations. One resource per event type; an empty list mutes that event without deleting the route.
lastping_alert_template Custom alert message bodies for one monitor, overriding the built-in wording. One resource owns the monitor's whole template map, because the API replaces the set rather than merging into it.
lastping_status_page A public or private status page over a chosen set of monitors. Public slugs are globally unique across every LastPing account — see the note below.
lastping_api_key A long-lived API key for automation that outlives the run. The plaintext is stored in Terraform state; prefer the ephemeral resource below when you can.
ephemeral lastping_api_key A short-lived key minted for one run and revoked when it ends. Never written to plan or state. Requires Terraform 1.10 or later.
data lastping_project The project the configured key belongs to. Doubles as a credential smoke-test: a revoked key fails here at plan time rather than partway through an apply.
data lastping_monitors Every monitor in the project, optionally filtered by tag. Useful for building a status page from a tag instead of a hand-maintained list of ids, and for auditing monitors created outside Terraform.
data lastping_monitor A single monitor by id or slug — how you attach a route to a monitor Terraform does not manage, without importing it.
data lastping_destination A single destination by id or name. Reports only the non-secret target hint; a name matching more than one destination is an error, not an arbitrary pick.
data lastping_incidents One monitor's incident history, newest first. For outputs and reports — incident history moves without Terraform doing anything, so do not feed it into a managed resource.
data lastping_metrics The project's monitors as Prometheus gauges, exactly as the metrics endpoint returns them. For writing a scrape snapshot to disk or feeding a dashboard module.

Four things worth knowing before your first apply

The provider documentation covers all of these, but these are the ones that actually cost people an afternoon.

Alert templates use {token}, not Go templates

The placeholders are {check_name}, {status}, {event}, {cause}, {last_ping}, {schedule}, {incident_url}, {run_url}, {branch}, {commit}, {actor}, {failing_stage}, {duration}, {latency}, {status_code} and {url}.

A body containing {{.CheckName}} is accepted — it does not match the token pattern, so validation has nothing to object to — and then renders literally into a real alert at 3am. An unrecognised token in {token} shape, such as {check_nam}, is rejected at apply time and names the offending key. The trap is the syntax that looks like a template engine and is not one.

grace_s is required

Omit it and the API returns 400 — grace 0s outside [60, 31536000]. There is no default, deliberately: how long a job may be late before it counts as missing is a decision about that job, not something a monitoring tool should guess.

The exception is monitor_type = "http", where the server floors the effective grace at 2 × probe_interval_s so one slow probe cannot false-fire. Set grace_s there only to ask for more than the floor; a smaller value is rejected at plan time rather than being silently raised.

Status page slugs are global

Unusually, a status page slug is unique across every LastPing account, not scoped to your project — a public URL can only belong to one page anywhere. So a slug you have never seen may already be taken by a stranger, and a configuration that applied cleanly yesterday can fail to recreate today for that reason alone.

Because slug forces replacement and Terraform destroys before creating by default, renaming a public page releases its slug into that global namespace before claiming the new one. Add lifecycle { create_before_destroy = true } to public status pages.

Terraform will not adopt what it did not create

Routes and alert templates are both whole-set writes: one PUT replaces everything. So creating either on a monitor that already has them configured — through the dashboard, or over MCP — would silently overwrite someone else's alerting, with the next incident as the discovery channel.

The provider refuses instead, and tells you to terraform import them. That is the safe failure, and it is why Export as Terraform emits import blocks rather than plain resources.

One deliberate gap: binding a monitor to a CI provider is not manageable through Terraform. Doing so mints a write-once secret the API returns only at creation and can never read back, so the provider does not model it rather than pretending to. Configure CI binding in the dashboard — the export marks any monitor where this applies with a comment explaining why the attributes are missing.


Questions

Front-loaded answers — the most important fact first.

  • Does LastPing have a Terraform provider?

    Yes. The official provider is published on the public Terraform Registry as lastping-dev/lastping. Add source = "lastping-dev/lastping" and version = "~> 0.1" to your required_providers block and terraform init downloads and signature-verifies it like any other registry provider. It covers six managed resources, one ephemeral resource and six data sources, and needs Terraform 1.5 or later — 1.10 or later if you use the ephemeral API key.

  • Can I import monitors I already created in the dashboard?

    Yes, and you do not have to write the HCL yourself. Export as Terraform renders your existing monitors, destinations, routes, alert templates and status pages as HCL, and pairs every resource with a Terraform import block — so the first plan adopts what you already have rather than proposing to recreate it. Secrets are the one thing it cannot include: the API never returns them, so each secret attribute is emitted as a var. reference with a matching sensitive variable block for you to fill in.

  • Is the LastPing Terraform provider free?

    Yes. LastPing is free for individuals — no monitor cap, no feature gate — and the Terraform provider is part of that. The provider itself is open source (MPL-2.0, source on GitHub) and adds no cost of its own. The one free-tier limit that shows up in Terraform is status pages: one public status page per project, with private pages unlimited.

  • Which LastPing resources can Terraform manage?

    Six managed resources — lastping_monitor, lastping_destination, lastping_route, lastping_alert_template, lastping_status_page and lastping_api_key — plus an ephemeral lastping_api_key and six data sources. See the resource reference for the full list.

    The deliberate gap is binding a monitor to a CI provider: that mints a write-once secret the API returns only at creation and can never read back, so the provider does not model it. Configure CI binding in the dashboard; everything else is declarable.

  • Can an AI agent write the Terraform for me?

    Yes, and this is the intended path. The provider's schema and documentation are public on the Terraform Registry, so a coding agent can write correct HCL for a monitor in the same pull request that adds the job it watches — no separate ticket, no dashboard visit, and the reviewer sees both changes together.

    Agents that prefer to act on the live API rather than write files can use the LastPing MCP server instead. Both paths reach the same monitors, and Export as Terraform is how one created over MCP later becomes code.

  • How do I keep a LastPing API key out of Terraform state?

    Use the ephemeral lastping_api_key, which requires Terraform 1.10 or later. It mints a key for the duration of the run, hands it to a provider configuration, and revokes it when the run ends — nothing about it is written to plan or state. The key also carries a server-side expiry as a safety net, so a run that is killed before it can revoke its key still leaves a credential that stops working on its own.

  • Can I use the provider against a self-hosted LastPing?

    Yes. Set LASTPING_ENDPOINT in the environment, or the endpoint argument on the provider block. It defaults to https://app.lastping.dev.

TERRAFORM PROVIDER · v0.1.0 · FREE FOR INDIVIDUALS

Put your monitoring in the same repo as the jobs it watches.

Get a free API key, add one required_providers block, and declare your first monitor. Already have monitors? Export them as Terraform and adopt them with a clean plan.