Building a Kubernetes Operator for the sake of building a Kubernetes Operator

A simple operator to sync Terraform outputs into Kubernetes ConfigMaps and Secrets

The Problem

At my current $job, we recently migrated to ArgoCD from Terraform for application deployments ๐Ÿ™. With that came an interesting challenge: how do we pass Terraform outputs into Kubernetes manifests?

For example, our AWS Managed Prometheus endpoint lives in Terraform state, but our apps deployed via ArgoCD need that URL. Sure, we could use External Secrets Operator (and we do!), but it adds an extra layer of indirection when you just want to see what values are being injected into pods.

The Solution: TFOut

After exploring what’s hot in 2025 for building Kubernetes operators, I had a dabble with Metacontroller but ultimately chose Kubebuilder - the same framework powering projects like Karpenter.

So Claude and I vibed out for the afternoon and built TFOut - a simple operator that syncs Terraform state outputs from S3 directly into Kubernetes ConfigMaps and Secrets.

๐ŸŽฏ Quick Example

Here’s all you need to sync your Terraform outputs:

apiVersion: tfout.wibrow.net/v1alpha1
kind: TerraformOutputs
metadata:
  name: my-terraform-outputs
spec:
  backends:
    - s3:
        bucket: my-terraform-state
        key: prod/terraform.tfstate
        region: eu-west-1
        roleArn: arn:aws:iam::123456789012:role/terraform-sync-role
  syncInterval: 5m
  targetNamespace: production

โœจ Result: All your Terraform outputs are automatically available as ConfigMaps and Secrets in Kubernetes. Sensitive outputs go into Secrets, the rest into ConfigMaps.

๐Ÿ“ฆ What’s in the Box?

Building a production-ready operator in an afternoon? Here’s what Claude and I shipped:

Core Features

  • ๐Ÿš€ Operator - Go + Kubebuilder framework
  • ๐Ÿ“Š Helm charts - Easy deployment
  • ๐Ÿ“š Documentation - MkDocs + GitHub Pages
  • ๐Ÿ”„ CI/CD - GitHub Actions workflows
  • ๐Ÿงช E2E tests - Kind-based testing
  • ๐Ÿ“ˆ Observability - Prometheus metrics + Grafana dashboards
  • ๐Ÿ” Security - Full RBAC setup

๐Ÿ”ง How It Works

The reconciliation loop is beautifully simple:

  1. Watch - Monitor TerraformOutputs resources
  2. Fetch - Pull state from S3 (only if ETag changed)
  3. Parse - Extract all Terraform outputs
  4. Sync - Create/update ConfigMaps and Secrets
  5. Merge - Handle multiple backends gracefully

Example Output

$ kubectl get configmap my-terraform-outputs -o yaml
data:
  api_endpoint: https://api.example.com
  cdn_domain: cdn.example.com
  database_host: postgres.internal.example.com

๐Ÿ’ก Development Journey

The process was refreshingly straightforward:

  1. Scaffold - Kubebuilder did the heavy lifting
  2. Focus - Wrote the core reconciliation logic
  3. Iterate - Claude helped with boilerplate (Helm templates, GitHub Actions, tests)
  4. Polish - Added metrics, multi-backend support, and proper error handling

๐ŸŒŸ Production-Ready Features

๐Ÿ” Smart Change Detection

Uses S3 ETags to avoid unnecessary syncs - only fetches when state actually changes.

๐Ÿ”€ Multi-Backend Support

Merge outputs from multiple Terraform states seamlessly:

backends:
  - s3:
      bucket: prod-state
      key: prod/terraform.tfstate
  - s3:
      bucket: other-state
      key: other/terraform.tfstate

๐Ÿ“Š Full Observability

  • Sync duration metrics
  • Error count tracking
  • Output count monitoring
  • Grafana dashboard included!

๐Ÿ”’ Enterprise Security

  • IAM roles for S3 access
  • Full RBAC implementation
  • Automatic sensitive data detection
  • Secure Secret creation

โœ… Comprehensive Testing

  • Unit tests with high coverage
  • E2E tests using Kind clusters
  • Integration tests for S3 operations

๐Ÿš€ Get Started

Installation

The code is on GitHub: github.com/swibrow/tfout โญ

# Add the Helm repository
helm repo add tfout https://swibrow.github.io/tfout
helm repo update

# Install the operator
helm install tfout tfout/tfout \
  --namespace tfout \
  --create-namespace

๐Ÿ” AWS Pod Identity Setup

Configure IAM roles for secure S3 access:

module "pod_identity" {
  source  = "terraform-aws-modules/eks-pod-identity/aws"
  version = "1.11.0"

  name                 = "tfout"
  description          = "Test tfout"
  attach_custom_policy = true

  association_defaults = {
    namespace       = "tfout"
    service_account = "tfout"
  }
  associations = {
    platform = {
      cluster_name = module.k8s_platform.eks.cluster_name
    }
  }

  policy_statements = [
    {
      effect = "Allow"
      actions = [
        "s3:*"
      ]
      resources = ["*"]
    }
  ]
}

๐Ÿ“ Complete Example

Here’s a full example with multiple backends and custom naming:

apiVersion: tfout.wibrow.net/v1alpha1
kind: TerraformOutputs
metadata:
  name: platform-outputs
spec:
  backends:
    # Production state
    - s3:
        bucket: my-terraform-state
        key: prod/terraform.tfstate
        region: eu-west-1
        roleArn: arn:aws:iam::123456789012:role/terraform-sync-role

    # Shared infrastructure state
    - s3:
        bucket: my-terraform-state
        key: shared/terraform.tfstate
        region: eu-west-1
        roleArn: arn:aws:iam::123456789012:role/terraform-sync-role

  syncInterval: 5m
  targetNamespace: production
  configMapName: platform-config
  secretName: platform-secrets

Using the Outputs in Your Apps

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: app
    env:
    - name: API_ENDPOINT
      valueFrom:
        configMapKeyRef:
          name: platform-config
          key: api_endpoint
    - name: DATABASE_PASSWORD
      valueFrom:
        secretKeyRef:
          name: platform-secrets
          key: database_password

๐ŸŽฏ Key Takeaways

  1. Kubernetes operators aren’t scary - With modern tooling, you can build production-ready operators quickly
  2. AI accelerates development - Claude handled the boilerplate while I focused on business logic
  3. Sometimes simple is better - Not every problem needs a complex solution
  4. The ecosystem has matured - Tools like Kubebuilder make operator development accessible

๐Ÿค Join the Fun!

If you made it this far, thanks for reading! I hope this inspires you to build that operator you’ve been thinking about. Your mum will definitely be impressed! ๐Ÿ˜„

Got questions? Find me on:


Built with โค๏ธ and Claude in an afternoon. Sometimes the best tools are the ones that scratch your own itch.