Observability as Code
Uptrace lets you define your observability setup as code. Organizations, projects, alert monitors, notification channels, and team access can all be managed with Terraform using the official Uptrace provider — making your monitoring setup reproducible, auditable, and consistent across environments.
Dashboards and data transformation rules are managed as YAML files, imported via the Uptrace UI or MCP Server.
Why Observability as Code
When your observability config lives in code rather than UI state:
- Reproducibility — replicate your monitoring setup across environments from the same source files
- Version control — every change is tracked in git history
- Portability — copy a working setup from staging to production
- Collaboration — the whole team can review and propose changes via pull requests
- Automation — integrate with CI/CD pipelines using
terraform planandterraform apply
What you can manage as code
| Component | Method | What it controls |
|---|---|---|
| Organizations & projects | Terraform | Orgs, projects, retention policies, ingest tokens |
| Users & teams | Terraform | User invitations, org roles, team membership, project access |
| Monitors | Terraform | Alert thresholds, anomaly detection, MQL queries |
| Notification channels | Terraform | Slack, PagerDuty, OpsGenie, webhooks, and more |
| Dashboards | YAML | Chart layouts, metric queries, table views, bundled monitors |
| Transformations | YAML | Attribute renaming, sampling rules, drop filters |
Terraform provider
Install the Uptrace Terraform provider and configure it with your API endpoint and token:
terraform {
required_providers {
uptrace = {
source = "uptrace/uptrace"
}
}
}
provider "uptrace" {
endpoint = "https://api.uptrace.dev"
token = var.uptrace_token
}
variable "uptrace_token" {
type = string
sensitive = true
}
The provider also reads from UPTRACE_ENDPOINT and UPTRACE_TOKEN environment variables. For self-hosted Uptrace, point endpoint to your instance (e.g., http://localhost:14318).
Organizations and projects
Create an organization, a project with a 30-day span retention policy, and an ingest token:
resource "uptrace_org" "main" {
name = "My Organization"
}
resource "uptrace_project" "api" {
org_id = uptrace_org.main.id
name = "api"
span_retention = "30d"
}
resource "uptrace_project_token" "api" {
project_id = uptrace_project.api.id
name = "default"
}
output "project_dsn" {
description = "Ingest DSN — pass this to your OpenTelemetry SDK"
value = uptrace_project_token.api.dsn
sensitive = true
}
Projects support per-signal retention and query time-range defaults:
| Attribute | Description |
|---|---|
span_retention | Data retention for spans (e.g. 30d, 2w) |
log_retention | Data retention for logs |
event_retention | Data retention for events |
metric_retention | Data retention for metrics |
span_time_range | Default query time range for spans |
log_time_range | Default query time range for logs |
event_time_range | Default query time range for events |
group_by_env | Group spans by environment |
group_funcs_by_service | Group functions by service |
Users and teams
Invite users, assign organization roles, and manage team-based project access:
resource "uptrace_user" "alice" {
email = "alice@example.com"
}
resource "uptrace_org_user" "alice" {
org_id = uptrace_org.main.id
user_id = uptrace_user.alice.id
role = "admin"
}
resource "uptrace_team" "platform" {
org_id = uptrace_org.main.id
name = "Platform"
}
resource "uptrace_team_user" "alice_platform" {
org_id = uptrace_org.main.id
team_id = uptrace_team.platform.id
org_user_id = uptrace_org_user.alice.id
}
resource "uptrace_team_project" "platform_api" {
org_id = uptrace_org.main.id
team_id = uptrace_team.platform.id
project_id = uptrace_project.api.id
}
Available organization roles: owner, admin, member, viewer, billing_manager, collaborator.
Monitors
Define metric monitors with manual thresholds or automatic anomaly detection:
resource "uptrace_metric_monitor" "error_rate" {
project_id = uptrace_project.api.id
name = "High error rate"
notify_everyone_by_email = true
params = {
metrics = [
{ name = "uptrace_tracing_spans", alias = "$spans" }
]
query = "_error_rate | where _type = \"httpserver\" | group by service_name"
column = { name = "_error_rate" }
num_eval_points = 5
detector = {
manual = {
max_value = 0.05
}
}
}
}
resource "uptrace_metric_monitor" "latency_anomaly" {
project_id = uptrace_project.api.id
name = "HTTP latency spike"
params = {
metrics = [
{ name = "uptrace_tracing_spans", alias = "$http_duration" }
]
query = "avg($http_duration)"
column = { name = "avg($http_duration)", unit = "milliseconds" }
detector = {
auto = {
tolerance = "medium"
training_period = 86400000
}
}
}
}
Error monitors watch for trend anomalies in log and span data:
resource "uptrace_error_monitor" "log_errors" {
project_id = uptrace_project.api.id
name = "Notify on all errors"
params = {
metrics = [
{ name = "uptrace_tracing_logs", alias = "$logs" }
]
query = "sum($logs) | where _system in (\"log:error\", \"log:fatal\")"
}
}
Notification channels
Route alerts to your team's tools. The provider supports Slack, PagerDuty, OpsGenie, Telegram, Microsoft Teams, Google Chat, Mattermost, Pushover, ServiceNow, incident.io, Alertmanager, and generic webhooks.
resource "uptrace_slack_channel" "alerts" {
project_id = uptrace_project.api.id
name = "slack-alerts"
priorities = ["high", "medium"]
auth_method = "webhook"
webhook_url = "https://hooks.slack.com/services/T00/B00/XXXX"
}
resource "uptrace_pagerduty_channel" "oncall" {
project_id = uptrace_project.api.id
name = "pagerduty-oncall"
priorities = ["high"]
routing_key = var.pagerduty_routing_key
severity = "critical"
}
resource "uptrace_webhook_channel" "custom" {
project_id = uptrace_project.api.id
name = "custom-webhook"
priorities = ["high", "medium", "low"]
url = "https://example.com/hooks/alert"
}
Each channel can filter alerts by priority and optionally restrict to specific monitors using monitor_ids:
resource "uptrace_slack_channel" "critical_only" {
project_id = uptrace_project.api.id
name = "critical-errors"
priorities = ["high"]
auth_method = "webhook"
webhook_url = "https://hooks.slack.com/services/T00/B00/XXXX"
match_all = false
monitor_ids = [uptrace_error_monitor.log_errors.id]
}
Importing existing resources
Import resources already created in the UI into Terraform state:
terraform import uptrace_org.main <org_id>
terraform import uptrace_project.api <project_id>
terraform import uptrace_project_token.api <project_id>:<token_id>
terraform import uptrace_metric_monitor.error_rate <project_id>:<monitor_id>
terraform import uptrace_slack_channel.alerts <project_id>:<channel_id>
Dashboards
Dashboard templates are YAML files. Uptrace activates them automatically when matching metrics arrive. You can export any dashboard as YAML, modify it, and import it across environments via the Uptrace UI.
schema: v2
name: 'API Service: Overview'
tags: [otel, app]
version: v26.01.01
table:
metrics:
- http_server_request_duration as $dur
query:
- group by service_name
- p99($dur) as p99_ms
grid_sections:
- title: Latency
items:
- title: p99 latency by service
metrics:
- http_server_request_duration as $dur
query:
- p99($dur) group by service_name
Alert monitors can be bundled directly inside dashboard templates — so dashboards and their alerts ship together as one file. See Dashboard YAML Templates for the full schema.
Transformations
Transformation rules are YAML operations applied to every incoming span, log, or metric before storage. Keeping them in files means your data pipeline is auditable and reproducible.
Drop noisy spans before they reach storage:
name: Drop healthcheck spans
scope: [spans]
type: drop
if: spanName() == "GET /healthz" && spanStatusCode() != "error"
Sample successful requests while keeping all errors:
name: Sample 10% of successful requests
scope: [spans]
type: sample
fraction: 0.1
if: spanStatusCode() != "error"
See Transformations for all available operations.
A complete workflow
A typical project layout with Uptrace config in git:
my-repo/
└── uptrace/
├── main.tf # provider + org + projects
├── monitors.tf # metric and error monitors
├── channels.tf # notification channels
├── teams.tf # users, teams, project access
├── variables.tf # tokens, webhook URLs
├── outputs.tf # project DSNs
├── dashboards/
│ ├── api-service.yml # dashboard + bundled monitors
│ └── worker-service.yml
└── transformations/
├── drop-healthchecks.yml
└── sample-success.yml
Applying the config to a new environment:
terraform apply— creates orgs, projects, tokens, monitors, channels, and team access- Import dashboard YAML files via Metrics → Dashboards → Import
- Apply transformation rules via Project → Transformations → New Operation → From YAML
Step 1 is fully automated and can run in CI/CD pipelines. Dashboards and transformations (steps 2–3) are applied through the UI or via the MCP Server.
Automating dashboards via MCP
If you use AI assistants (Claude, Cursor, Continue), Uptrace's built-in MCP Server exposes dashboard management as tools — no UI required. You can create, update, and delete dashboards programmatically from your AI assistant using the same YAML format.
claude mcp add --transport http uptrace https://<your-uptrace-host>/mcp/<project_id> \
--header "Authorization: Bearer <your-user-token>"
Once connected, an AI assistant can apply your dashboard YAML files directly:
| MCP Tool | What it does |
|---|---|
create_dashboard_from_template | Create a new dashboard from YAML |
update_dashboard_from_template | Update an existing dashboard from YAML |
get_dashboard_yaml | Export a dashboard as YAML |
delete_dashboard | Delete a dashboard by ID |
See MCP Server for setup instructions.
Self-hosted: seed data
For self-hosted Uptrace, you can also define initial users and projects directly in uptrace.yml:
seed_data:
orgs:
- key: my_org
name: My Organization
projects:
- key: production
name: Production
org: my_org
- key: staging
name: Staging
org: my_org
The Terraform provider works with both Cloud and self-hosted Uptrace. For self-hosted setups, set the endpoint to your instance URL.