Score code, devs, and debt fast.

Start free trial
SitePoint Premium
Stay Relevant and Grow Your Career in Tech
  • Premium Results
  • Publish articles on SitePoint
  • Daily curated jobs
  • Learning Paths
  • Discounts to dev tools
Start Free Trial

7 Day Free Trial. Cancel Anytime.

When I first started using Terraform, I assumed the hard part would be learning HCL syntax and understanding providers. It turned out the real challenge was something else entirely: managing the same infrastructure across dev, staging, and production without creating drift or accidentally breaking prod.

After managing infrastructure for systems serving millions of users across multiple Oracle Cloud tenancies, I’ve learned that environment structure matters more than clever Terraform modules. The biggest failures I’ve seen weren’t caused by bad code, but by unclear boundaries between environments.

Why Copy-Paste Infrastructure Fails

Early on, I made a common mistake. I got Terraform working in dev, copied the entire directory for staging, tweaked a few variables, then copied it again for prod. It felt pragmatic and fast.

It worked until the first real change. A firewall rule needed updating. I fixed dev, then staging, and forgot prod. Two weeks later, prod had different security rules, and no one could explain why.

The real damage showed up during an incident. A feature worked in staging but failed in prod. We spent hours debugging application code before realizing the environments were no longer equivalent. Infrastructure drift had quietly crept in.

Copy-paste infrastructure always looks harmless at the beginning. It becomes technical debt the moment your systems start changing.

Separate State Files Are Mandatory

One lesson I learned quickly is that every environment needs its own state file. Sharing state across environments is an accident waiting to happen.

I’ve seen teams use Terraform workspaces with a single backend to save time or storage. It works right up until someone runs terraform apply thinking they’re in dev when they’re actually targeting prod. That mistake is irreversible.

With separate state files, each environment is isolated. A mistake in dev stays in dev. The blast radius is limited by design.

Each environment has its own remote backend and access controls. Production state requires elevated permissions. Those friction points are intentional and valuable.

Environment-Specific Modules Beat Conditional Logic

The biggest mental shift for me was accepting that environments should be consistent, but not identical.

Production needs high availability, backups, and capacity. Development doesn’t. Trying to force a single module to handle all of that with conditionals quickly turns modules into unreadable messes.

What worked was separating concerns:

  • Base modules define resources and shared logic.
  • Environment wrappers define scale, durability, and risk tolerance.

When core behavior changes, I update the base module once. When environment needs differ, I change the wrapper. This keeps modules readable and environments explicit.

Variable Files Make Deployments Explicit

For configuration differences that don’t justify separate modules, per-environment variable files work well.

Each environment has its own .tfvars file, and deployments always specify it explicitly. This removes ambiguity. You can’t accidentally apply dev settings to prod without being very deliberate.

That explicitness matters more than convenience when real systems are on the line.

Secrets Management Is Not Optional

The closest we came to a serious security incident was discovering production credentials committed in a Terraform variable file. That triggered immediate credential rotation across all environments.

After that, we enforced a strict rule: secrets never live in Terraform files.

In Oracle Cloud, we rely on IAM for access control, OCI Vault for secrets, and Terraform data sources to fetch secrets at runtime. Terraform references secrets, but never owns them. Source control stays clean, and rotation becomes manageable.

Why We Avoid Terraform Workspaces

Terraform workspaces look attractive on paper. Same code, multiple environments, easy switching.

In practice, they’re too easy to misuse. The active workspace is invisible unless you actively check it. We had multiple near-misses where production resources were almost modified unintentionally.

After that, we stopped using workspaces entirely. Separate directories with explicit backends make it obvious where you are and what you’re about to change. That clarity is worth far more than convenience.

The Structure That Actually Holds Up

The setup that’s kept us sane is simple:

  • Shared modules for reusable infrastructure
  • Separate environment directories
  • Isolated state backends
  • Explicit variable files per environment

When you deploy to prod, the path, the backend, and the variables all reinforce that you’re operating on production. There’s no ambiguity.

Why Staging Exists

Every change follows the same path:

  1. Apply in dev
  2. Apply in staging and run integration tests
  3. Apply in prod during a controlled window

We never skip staging. It exists to catch scale-related failures that dev can’t expose.

Once, a firewall change worked perfectly in dev. In staging, with realistic traffic, it blocked health checks and took the service down. That failure never reached production because staging caught it first.

The Real Lesson

Managing infrastructure across environments isn’t about finding the perfect Terraform abstraction. It’s about designing guardrails that prevent mistakes and make risky actions obvious.

Separate state files, explicit variable files, isolated environments, and proper secrets management are not exciting ideas. They are defensive ones. And they’ve prevented far more incidents than any clever Terraform trick ever could.

If you manage multiple environments and haven’t had a close call yet, you will. Build guardrails now. You’ll be grateful later.

About the author: Principal Software Engineer with 14 years of experience building and operating cloud infrastructure at scale. Currently at Oracle Cloud Infrastructure. Previously at Amazon, Salesforce, IBM, and Tableau.

© 2000 – 2026 SitePoint Pty. Ltd.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.