Skip to main content
Back to blog

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.

Flaggr TeamFebruary 25, 20266 min read
terraforminfrastructure-as-codeDevOpsfeature-flags

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/flags

Getting 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 resource

Each 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:
        - apply

When 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

  1. Generate an API token in the Flaggr dashboard with admin permissions
  2. Install the provider: Add the flaggr/flaggr provider to your Terraform configuration
  3. Import existing resources: Use terraform import to bring current flags under management
  4. 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.