TerraformPilot

Terraform

Terraform Consul Backend and Provider

Use HashiCorp Consul with Terraform for state storage, service discovery, and KV configuration. Consul backend, provider resources, and service mesh...

LLuca Berton1 min read

Quick Answer

#
terraform {
  backend "consul" {
    address = "consul.example.com:8500"
    scheme  = "https"
    path    = "terraform/myapp/state"
  }
}

Consul Backend Configuration

#
terraform {
  backend "consul" {
    address      = "consul.example.com:8500"
    scheme       = "https"
    path         = "terraform/${var.project}/${var.environment}"
    access_token = var.consul_token  # Or CONSUL_HTTP_TOKEN env var
    lock         = true
    gzip         = true  # Compress state
  }
}

Environment Variables

#
export CONSUL_HTTP_ADDR="consul.example.com:8500"
export CONSUL_HTTP_TOKEN="your-acl-token"
export CONSUL_HTTP_SSL=true
# Minimal config with env vars
terraform {
  backend "consul" {
    path = "terraform/myapp/production"
  }
}

Consul Provider

#
provider "consul" {
  address    = "consul.example.com:8500"
  scheme     = "https"
  token      = var.consul_token
  datacenter = "dc1"
}

KV Store Management

#
# Write configuration values
resource "consul_keys" "app_config" {
  key {
    path  = "config/myapp/database_url"
    value = "postgres://${aws_db_instance.main.address}:5432/myapp"
  }
 
  key {
    path  = "config/myapp/redis_url"
    value = "redis://${aws_elasticache_replication_group.main.primary_endpoint_address}:6379"
  }
 
  key {
    path  = "config/myapp/environment"
    value = var.environment
  }
}
 
# Read configuration values
data "consul_keys" "shared" {
  key {
    name = "vpc_id"
    path = "terraform/networking/vpc_id"
  }
 
  key {
    name    = "domain"
    path    = "config/shared/domain"
    default = "example.com"
  }
}
 
resource "aws_instance" "web" {
  subnet_id = data.consul_keys.shared.var.vpc_id
  # ...
}

Service Registration

#
resource "consul_service" "web" {
  name    = "web"
  node    = "web-server-1"
  port    = 8080
  address = aws_instance.web.private_ip
 
  tags = ["production", "v2"]
 
  check {
    check_id = "web-health"
    name     = "HTTP Health Check"
    http     = "http://${aws_instance.web.private_ip}:8080/health"
    interval = "10s"
    timeout  = "5s"
  }
}

Service Discovery (Data Source)

#
data "consul_service" "database" {
  name = "postgres"
  tag  = "primary"
}
 
# Use discovered service address
locals {
  db_address = data.consul_service.database.service[0].address
  db_port    = data.consul_service.database.service[0].port
}

ACL Policies

#
resource "consul_acl_policy" "app" {
  name  = "app-policy"
  rules = <<-RULES
    key_prefix "config/myapp/" {
      policy = "read"
    }
    service "web" {
      policy = "write"
    }
    node_prefix "" {
      policy = "read"
    }
  RULES
}
 
resource "consul_acl_token" "app" {
  description = "App service token"
  policies    = [consul_acl_policy.app.name]
}

Intentions (Service Mesh)

#
resource "consul_config_entry" "web_to_db" {
  kind = "service-intentions"
  name = "postgres"
 
  config_json = jsonencode({
    Sources = [{
      Name       = "web"
      Action     = "allow"
      Precedence = 9
      Type       = "consul"
    }]
  })
}

Consul vs S3/GCS Backend

#
FeatureConsulS3/GCS
State lockingBuilt-inRequires DynamoDB/native
Service discovery
KV config store
Health checks
Setup complexityHigherLower
Best forMulti-service appsSimple state storage
#

Conclusion

#

Use the Consul backend when you're already running Consul for service discovery. It provides state locking without extra infrastructure (unlike S3+DynamoDB). The Consul provider is powerful for managing KV configuration, service registration, ACL policies, and service mesh intentions alongside your infrastructure.

#Terraform#Consul#HashiCorp#State Management#Infrastructure as Code

Share this article