Managing Feature Flags with Terraform: Infrastructure as Code for Feature Management
Learn how to manage feature flags as infrastructure with Terraform — declarative flag definitions, version-controlled rollouts, CI/CD integration, and drift detection.
Managing Feature Flags with Terraform: Infrastructure as Code for Feature Management
Feature flags live at the intersection of code and configuration. They control which features are active, who sees them, and how they roll out. Yet most teams manage flags through a UI — clicking buttons in a dashboard, disconnected from the code review and version control processes that govern everything else.
Terraform changes this. By defining feature flags as infrastructure code, you get the same benefits that IaC brought to servers, databases, and networks: version control, code review, drift detection, and reproducible environments.
Why Manage Flags with Terraform?
Version Control
Every flag change goes through a pull request. Reviewers see exactly what's changing — which flags, what targeting rules, what percentages. The git history becomes an audit trail:
commit a3f7b2c Enable dark mode for enterprise users
commit 8e1d9f4 Increase checkout-v2 rollout to 50%
commit 2b4c6a8 Add rate limit flag with 1000 req/min default
Reproducible Environments
Define flags once, apply to multiple environments:
# modules/flags/main.tf
variable "environment" {}
variable "rollout_percentage" { default = 0 }
resource "flaggr_flag" "new_checkout" {
service_id = var.service_id
key = "release-checkout-v2"
name = "Checkout V2"
type = "boolean"
environment = var.environment
default_value = false
targeting_rules {
percentage = var.rollout_percentage
}
}# environments/staging/main.tf
module "flags" {
source = "../../modules/flags"
environment = "staging"
service_id = module.service.id
rollout_percentage = 100 # Full rollout in staging
}
# environments/production/main.tf
module "flags" {
source = "../../modules/flags"
environment = "production"
service_id = module.service.id
rollout_percentage = 25 # Gradual rollout in production
}Drift Detection
terraform plan shows you when the actual flag state doesn't match your code. Someone toggled a flag in the UI? Terraform catches it:
~ resource "flaggr_flag" "dark_mode" {
~ enabled = true -> false # UI change detected
~ default_value = true -> false
}
CI/CD Integration
Flag changes flow through the same pipeline as infrastructure changes. Apply flags alongside the services that use them:
# .github/workflows/deploy.yml
jobs:
deploy:
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Deploy infrastructure
run: terraform apply -auto-approve
working-directory: terraform/infrastructure
- name: Apply feature flags
run: terraform apply -auto-approve
working-directory: terraform/flagsGetting Started with the Flaggr Terraform Provider
Provider Configuration
terraform {
required_providers {
flaggr = {
source = "flaggr/flaggr"
version = "~> 0.1"
}
}
}
provider "flaggr" {
api_url = "https://flaggr.dev"
api_token = var.flaggr_api_token # fgr_xxx token
}Creating a Project and Service
resource "flaggr_project" "main" {
name = "My Application"
slug = "my-app"
}
resource "flaggr_service" "api" {
project_id = flaggr_project.main.id
name = "API Service"
description = "Backend API service"
}
resource "flaggr_service" "web" {
project_id = flaggr_project.main.id
name = "Web Frontend"
description = "React web application"
}Defining Feature Flags
# Boolean flag with targeting rules
resource "flaggr_flag" "dark_mode" {
service_id = flaggr_service.web.id
key = "release-dark-mode"
name = "Dark Mode"
description = "Enable dark mode theme"
type = "boolean"
environment = "production"
enabled = true
default_value = false
targeting_rules {
conditions {
attribute = "plan"
operator = "in"
value = jsonencode(["pro", "enterprise"])
}
percentage = 100
}
}
# String flag for variant testing
resource "flaggr_flag" "cta_text" {
service_id = flaggr_service.web.id
key = "experiment-cta-text"
name = "CTA Button Text"
type = "string"
environment = "production"
enabled = true
default_value = "Get Started"
}
# Number flag for configuration
resource "flaggr_flag" "rate_limit" {
service_id = flaggr_service.api.id
key = "ops-rate-limit"
name = "API Rate Limit"
type = "number"
environment = "production"
enabled = true
default_value = 1000
}Setting Up Alerting
resource "flaggr_alert_channel" "slack" {
project_id = flaggr_project.main.id
name = "Engineering Slack"
type = "slack"
config = jsonencode({
webhook_url = var.slack_webhook_url
})
}
resource "flaggr_alert_rule" "high_error_rate" {
project_id = flaggr_project.main.id
name = "High Error Rate"
metric = "error_rate"
operator = "greater_than"
threshold = 5.0
window = "5m"
channel_id = flaggr_alert_channel.slack.id
enabled = true
}Using Data Sources
Read existing resources for reference in other configurations:
data "flaggr_project" "existing" {
slug = "my-app"
}
data "flaggr_flag" "checkout" {
key = "release-checkout-v2"
service_id = data.flaggr_service.api.id
environment = "production"
}
output "checkout_enabled" {
value = data.flaggr_flag.checkout.enabled
}Patterns for Flag Management
Environment Promotion
Use Terraform workspaces or modules to promote flag configurations across environments:
# variables.tf
variable "env_config" {
type = map(object({
rollout_percentage = number
enabled = bool
}))
default = {
development = { rollout_percentage = 100, enabled = true }
staging = { rollout_percentage = 100, enabled = true }
production = { rollout_percentage = 0, enabled = true }
}
}
resource "flaggr_flag" "feature" {
for_each = var.env_config
service_id = flaggr_service.api.id
key = "release-new-feature"
name = "New Feature"
type = "boolean"
environment = each.key
enabled = each.value.enabled
default_value = false
targeting_rules {
percentage = each.value.rollout_percentage
}
}Gradual Rollout via PR
Manage rollout stages through pull requests. Each PR bumps the percentage:
# PR #1: "Enable checkout-v2 canary (5%)"
targeting_rules {
percentage = 5
}
# PR #2: "Expand checkout-v2 to 25%"
targeting_rules {
percentage = 25
}
# PR #3: "GA checkout-v2 (100%)"
targeting_rules {
percentage = 100
}
# PR #4: "Remove checkout-v2 flag" — delete the resourceEach change is reviewed, approved, and has a clear audit trail in git.
Importing Existing Flags
Already have flags created through the UI? Import them into Terraform state:
# Import a project
terraform import flaggr_project.main <project-id>
# Import a service
terraform import flaggr_service.api <service-id>
# Import a flag (format: key:serviceId:environment)
terraform import flaggr_flag.dark_mode "release-dark-mode:svc_123:production"
# Import an alert channel (format: projectId:channelId)
terraform import flaggr_alert_channel.slack "proj_123:ch_456"After importing, run terraform plan to verify the state matches your configuration.
Flag Lifecycle as Code
Encode your team's flag lifecycle policy in Terraform:
locals {
# Flags older than 30 days at 100% should be removed
stale_flags = {
for k, v in var.flags : k => v
if v.created_before < timeadd(timestamp(), "-720h") && v.percentage == 100
}
}
# Output stale flags for cleanup review
output "flags_pending_removal" {
value = [for k, v in local.stale_flags : k]
}CI/CD Integration Patterns
GitHub Actions
name: Feature Flag Deployment
on:
push:
paths: ['terraform/flags/**']
branches: [main]
pull_request:
paths: ['terraform/flags/**']
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
working-directory: terraform/flags
- run: terraform plan -no-color
working-directory: terraform/flags
env:
TF_VAR_flaggr_api_token: ${{ secrets.FLAGGR_API_TOKEN }}
apply:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init && terraform apply -auto-approve
working-directory: terraform/flags
env:
TF_VAR_flaggr_api_token: ${{ secrets.FLAGGR_API_TOKEN }}GitOps with Atlantis
For teams using Atlantis for Terraform automation:
# atlantis.yaml
projects:
- dir: terraform/flags
workflow: flags
autoplan:
when_modified: ["*.tf", "*.tfvars"]
workflows:
flags:
plan:
steps:
- init
- plan
apply:
steps:
- applyWhen to Use Terraform vs the UI
Terraform excels for:
- Production flag changes — every change reviewed and auditable
- Environment setup — reproducible flag configurations across environments
- Rollout automation — staged percentage increases via PRs
- Team onboarding — new services get standard flags automatically
The UI is better for:
- Rapid iteration during development — toggling flags while debugging
- Emergency kill switches — when seconds matter, click the button
- Exploration — understanding flag state across services
- Non-technical stakeholders — product managers reviewing flag status
The best approach combines both: Terraform for production governance, the UI for development and emergency response. Flaggr's drift detection (via terraform plan) catches UI changes that should be codified.
Getting Started
- Generate an API token in the Flaggr dashboard with admin permissions
- Install the provider: Add the
flaggr/flaggrprovider to your Terraform configuration - Import existing resources: Use
terraform importto bring current flags under management - Start with non-critical flags: Practice the workflow before managing production-critical flags
The Flaggr Terraform provider documentation covers the full resource and data source reference with examples.
Get started with the Flaggr Terraform provider or read the infrastructure as code guide for detailed setup instructions.