Terraform Support
Manage OpenObserve as code. OpenObserve ships two complementary Terraform modules that together cover the full lifecycle: deploying the platform itself, and managing the resources that run inside it.
For support, reach out in the Slack channel.
| Module | Purpose | Source |
|---|---|---|
terraform-provider-openobserve |
Manage OpenObserve resources (streams, dashboards, users, organizations) as code | Terraform Registry · GitHub |
terraform-kubernetes-openobserve |
Deploy OpenObserve on Kubernetes via the official Helm chart, optionally provisioning the underlying AWS infrastructure | Terraform Registry · GitHub |
Both are Apache 2.0 licensed and work with OpenTofu as well as Terraform.
Prerequisites
- Terraform ≥ 1.9. OpenTofu is also supported. Verify with
terraform versionortofu version. - For the provider: a reachable OpenObserve endpoint and credentials (root user or a user with sufficient permissions). See Provider configuration for environment-variable alternatives.
- For the Kubernetes module: a Kubernetes cluster (≥ 1.25) with a default StorageClass, plus
kubectlconfigured for post-deploy verification. If you setcreate_aws_infrastructure = true, you also need AWS credentials with permissions to create VPCs, EKS clusters, S3 buckets, and IAM roles.
Terraform Provider
The OpenObserve provider manages streams, dashboards, users, and other resources via the OpenObserve REST API. Define them in .tf files and apply them through the standard plan → apply → review cycle — the same way you manage the rest of your infrastructure.
Supported resources
Managed (created and updated by the provider):
openobserve_stream— stream settings: data retention, indexed fields, full-text search keys, bloom filter fields, and partition keys. Required:name,org_id,stream_type(logs/metrics/traces).openobserve_dashboard— dashboard definitions.openobserve_user— user accounts.
Data sources (read-only lookups against an existing OpenObserve):
openobserve_organization— look up an existing organization.openobserve_stream— look up an existing stream's settings.
Provider configuration
| Argument | Description | Environment variable |
|---|---|---|
endpoint |
Base URL of the OpenObserve instance (e.g. https://openobserve.example.com) |
OPENOBSERVE_ENDPOINT |
username |
OpenObserve login email. Sensitive. | OPENOBSERVE_USERNAME |
password |
OpenObserve password. Sensitive. | OPENOBSERVE_PASSWORD |
org_id |
Default organization identifier used when a resource does not set org_id explicitly |
OPENOBSERVE_ORG_ID |
All arguments are optional in the schema — each falls back to its environment variable if not set in HCL.
Example: Configure the provider
terraform {
required_providers {
openobserve = {
source = "openobserve/openobserve"
version = "~> 0.1"
}
}
}
# Provider configuration using explicit values.
# Prefer environment variables for credentials in production:
# OPENOBSERVE_ENDPOINT, OPENOBSERVE_USERNAME, OPENOBSERVE_PASSWORD, OPENOBSERVE_ORG_ID
provider "openobserve" {
endpoint = "http://localhost:5080"
username = "root@example.com"
password = "Complexpass#123"
org_id = "default"
}
Example: Define a stream
resource "openobserve_stream" "app_logs" {
org_id = "default"
name = "app-logs"
stream_type = "logs"
settings {
data_retention = 30
full_text_search_keys = ["message", "body"]
index_fields = ["level", "service"]
bloom_filter_fields = ["trace_id"]
partition_keys {
field = "service"
types = ["value"]
}
}
}
Apply
The app-logs stream will appear in the OpenObserve UI under Streams, with the retention policy, full-text search keys, and index fields you declared.
Store credentials securely
Avoid committing username and password directly. Set OPENOBSERVE_USERNAME and OPENOBSERVE_PASSWORD as environment variables, use Terraform variables backed by a .tfvars file outside version control, or pull from a secrets backend such as AWS Secrets Manager or HashiCorp Vault.
Kubernetes Module
The Kubernetes module deploys OpenObserve through the official Helm chart (openobserve/openobserve-helm-chart) and covers both ends of the spectrum: a minimal single-node SQLite setup you can run locally, and a full production HA configuration with PostgreSQL, NATS, S3, Ingress, and per-component replica counts and resource limits.
Module prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Terraform | ≥ 1.9 | optional() with defaults requires 1.3+; mock provider tests require 1.7+ |
hashicorp/helm provider |
~> 2.16 | |
| Kubernetes cluster | ≥ 1.25 | EKS, GKE, AKS, or self-managed |
| Default StorageClass | — | Required for persistent volumes |
| PostgreSQL | ≥ 14 | Required for meta_store = "postgres" (HA) |
| S3-compatible bucket | — | Required for production data persistence |
Example: Minimal (development / single-node)
module "openobserve" {
source = "openobserve/openobserve/kubernetes"
version = "~> 1.0"
auth = {
root_user_email = "admin@example.com"
root_user_password = "ChangeMe123!"
}
# Single-node local mode: no PostgreSQL or NATS required
meta_store = "sqlite"
cluster_coordinator = "local"
queue_store = "local"
nats = { enabled = false }
}
Access the UI:
Example: Production HA (PostgreSQL + NATS + S3)
module "openobserve" {
source = "openobserve/openobserve/kubernetes"
version = "~> 1.0"
auth = {
root_user_email = var.root_user_email
root_user_password = var.root_user_password
postgres_dsn = var.postgres_dsn
s3_access_key = var.s3_access_key # omit to use IRSA
s3_secret_key = var.s3_secret_key
}
replica_count = {
ingester = 3
querier = 2
router = 2
compactor = 1
alertmanager = 1
}
meta_store = "postgres"
cluster_coordinator = "nats"
queue_store = "nats"
s3 = {
provider = "s3"
region = "us-east-1"
bucket_name = "my-openobserve-data"
}
ingress = {
enabled = true
class_name = "nginx"
host = "openobserve.example.com"
tls_secret_name = "openobserve-tls"
annotations = {
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
}
}
persistence = {
ingester = { size = "100Gi", storage_class = "gp3" }
querier = { size = "100Gi", storage_class = "gp3" }
alertmanager = { size = "10Gi", storage_class = "gp3" }
}
resources = {
ingester = {
requests = { memory = "2Gi", cpu = "500m" }
limits = { memory = "8Gi", cpu = "2000m" }
}
querier = {
requests = { memory = "2Gi", cpu = "500m" }
limits = { memory = "8Gi", cpu = "2000m" }
}
}
nats = { enabled = true }
minio = { enabled = false }
}
Example: Enterprise Edition
Point the image block at the enterprise container repository:
module "openobserve" {
source = "openobserve/openobserve/kubernetes"
version = "~> 1.0"
image = {
repository = "o2cr.ai/openobserve/openobserve-enterprise"
tag = "v0.80.2"
}
# ... rest of your configuration
}
Optional: Provision AWS Infrastructure
Set create_aws_infrastructure = true and the module also provisions the underlying:
- VPC with public and private subnets
- EKS cluster with managed node groups
- S3 bucket for telemetry data
- IAM roles for IRSA (IAM Roles for Service Accounts)
This is the fastest path from "empty AWS account" to "running OpenObserve cluster". For an alternative non-Terraform path, see Amazon EKS.
Passing arbitrary Helm values
Any chart setting not exposed as a first-class variable can be passed through extra_values (merged last, highest precedence):
module "openobserve" {
source = "openobserve/openobserve/kubernetes"
version = "~> 1.0"
# ... required variables
extra_values = [<<-EOT
enterprise:
enabled: true
config:
ZO_SWAGGER_ENABLED: "true"
ZO_PROMETHEUS_ENABLED: "true"
EOT
]
}
Capacity recommendations
Set capacity.ingestion_gb_per_day and the module exposes outputs with:
- Recommended deployment mode (single-node vs HA)
- Recommended replica counts per component
- Recommended EKS instance type and node count
- S3 storage estimate
- Estimated monthly cost
These are informational outputs only — set replica_count explicitly to override them. When create_aws_infrastructure = true, they're also used as defaults for aws_config unless overridden.
Reference data (256 GB/day):
- Single-node: 5 cores, ~$179/month
- HA: 25 cores, ~$927/month
Architecture
┌─────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
Ingest / Query ───►│ router (stateless, horizontally scalable) │
│ │ │ │
│ ingester ◄────► querier │
│ (WAL + disk) (disk cache) │
│ │ │ │
│ compactor alertmanager │
│ │ │
│ NATS (bundled or external) │
│ │ │
└──────┼─────────────────────────────────────-─┘
│
┌───────────┼──────────────┐
│ │ │
PostgreSQL S3 Object Store
(metadata) (long-term data)
Example configurations
| Example | Description |
|---|---|
examples/minimal |
Single-node SQLite deployment for development |
examples/complete |
Production HA: PostgreSQL + NATS + S3 + Ingress + TLS |
Upgrading the chart version
- Check the OpenObserve Helm chart releases for breaking changes.
- Update
chart_versionin your module call. - Run
terraform planand review the diff before applying. - For major chart version bumps, run
terraform applyduring a maintenance window.
Full input and output reference
For the complete list of inputs (auth, replica_count, persistence, resources, ingress, s3, aws_config, affinity, tolerations, node_selector, image_pull_secrets, etc.) and outputs (http_endpoint, grpc_endpoint, ingress_host, aws_infrastructure, capacity_recommendations, etc.), see the module README on GitHub.
Using the two modules together
A common production pattern:
- Use the Kubernetes module in one Terraform configuration to stand up the cluster.
- Use the provider in a separate configuration (with the deployment's endpoint as input) to declare the streams, dashboards, users, and organizations that should exist on it.
Keeping the two layers in separate state files makes it safe to iterate on resources (streams, dashboards) without risking the underlying cluster, and vice versa.
OpenTofu
Both modules are compatible with OpenTofu. Replace terraform with tofu in any command above:
No additional configuration is required.
License
Both modules are released under the Apache 2.0 license.
Need help: