Blog post 14 min read

OpenTofu Secrets Management with Infisical: A Practical Integration Guide

Published on
Blog image

Key Takeaways

  • OpenTofu has no built-in secrets management. Its design relies on external tooling, which means teams need a deliberate strategy for handling credentials across plan, apply, and state storage.
  • Common workarounds create risk. .tfvars files, environment variables, and hardcoded values leak into Git history, CI logs, and crash reports. Even OpenTofu's native state encryption only protects secrets at rest in the state file, not during execution or in logs.
  • Infisical integrates directly as an OpenTofu provider. Secrets are fetched at runtime, scoped by environment, and controlled with role based access. No .tfvars files, no shared static keys, no custom glue code.
  • Ephemeral resources keep secrets out of state entirely. Available in OpenTofu v1.11+, ephemeral resources fetch secrets during plan/apply without persisting them to the state file.
  • The bootstrapping problem is solvable. OIDC authentication eliminates the need to store long lived Infisical credentials in your CI/CD pipelines. For environments without OIDC support, Universal Auth with short lived tokens provides a secure fallback.
  • This replaces complex Vault workflows with a ready to use integration. Where Vault requires custom provider configurations, Raft cluster management, and often a dedicated team, Infisical provides the same secret injection capabilities as a managed, Postgres backed service with built in workflows.

The Problem: OpenTofu Needs External Secrets Management

OpenTofu emerged as the community's open source fork of Terraform after HashiCorp changed its licensing. It has become the default IaC tool for teams that want infrastructure provisioning without vendor lock in. But like Terraform before it, OpenTofu does not include built-in secrets management. Its architecture assumes you will bring your own solution.

This is a real operational gap, not a theoretical one. Secrets surface at three points in every OpenTofu run:

  • During planning: tofu plan loads credentials from variables, .tfvars files, or environment variables to validate your configuration. If a variable is not marked as sensitive, its value can appear in console output and logs.
  • During apply: tofu apply uses those credentials to authenticate with cloud providers, configure services, and pass values to APIs. Misconfigurations here expose credentials in API calls and build logs.
  • In state storage: The state file stores resource attributes, including secret values, after a successful apply. Even with OpenTofu's native state encryption (which encrypts the file at rest), the values are decrypted and available in memory during execution.

Most teams start with one of two approaches: .tfvars files or environment variables. Both have well documented weaknesses. A .tfvars file committed to Git puts credentials in version history permanently. Environment variables are better, but they can still be inspected by other processes, dumped in crash reports, or printed to build logs by a careless echo statement.

Teams that have been through these problems often turn to HashiCorp Vault. But Vault introduces its own complexity: Raft based clustering, a dedicated operations team, custom provider configurations, and a learning curve steep enough that some organizations abandon the effort during deployment.

Infisical takes a different approach. It is an open source secrets management platform that runs on Postgres (not proprietary infrastructure), ships with built in workflows and a UI, and integrates with OpenTofu as a native provider. The goal of this guide is to show you exactly how that integration works, what the code looks like, and how to structure it for production use.

How Infisical Integrates with OpenTofu

Infisical maintains a Terraform provider that works with both Terraform and OpenTofu. You declare it in your configuration, authenticate with a machine identity, and fetch secrets at runtime. No .tfvars files, no manual encryption, no custom scripts.

Here is how to set it up from scratch.

Step 1: Declare the Provider

Add the Infisical provider to your required_providers block:

terraform {
  required_providers {
    infisical = {
      source = "infisical/infisical"
    }
  }
}

This tells OpenTofu to download the Infisical provider from the registry. No version pinning is shown here for simplicity, but in production you should pin to a specific version to avoid unexpected changes.

Step 2: Authenticate with a Machine Identity

Infisical uses machine identities to authenticate non-human clients. You have two options: OIDC Auth (recommended for cloud based CI/CD) and Universal Auth (for environments without OIDC support).

Option A: OIDC Authentication (Recommended)

If your CI/CD runs on GitHub Actions, GitLab CI, AWS, GCP, or Azure, use OIDC. This eliminates the need to store any long lived Infisical credentials in your pipeline. Your CI platform issues a short lived token, and Infisical validates it directly:

provider "infisical" {
  host = "https://app.infisical.com"  # Omit for Infisical Cloud default

  auth = {
    oidc = {
      identity_id = "your-machine-identity-id"
    }
  }
}

The identity_id is the machine identity you create in Infisical's dashboard. When OpenTofu runs in your CI pipeline, the provider exchanges the platform's OIDC token for a short lived Infisical access token. No client secrets to rotate, no static credentials to leak.

Why this matters: OIDC solves the bootstrapping problem. Without it, you need to store Infisical credentials somewhere to access Infisical, which is the same secrets to access secrets problem you are trying to solve. OIDC lets your cloud provider vouch for the runner's identity directly.

Option B: Universal Auth

For environments that do not support OIDC (on premises CI runners, custom build systems, local development), use Universal Auth with a client ID and secret:

provider "infisical" {
  host = "https://your-infisical-instance.com"  # Required for self-hosted
  auth = {
    universal = {
      client_id     = var.infisical_client_id
      client_secret = var.infisical_client_secret
    }
  }
}

In CI/CD, pass these values as pipeline secrets (GitHub Actions secrets, GitLab CI variables, etc.), never as .tfvars or hardcoded values. The Infisical client secret should be scoped to the narrowest permissions possible and rotated on a schedule.

Warning: This approach still requires storing a credential somewhere in your pipeline. Use short lived tokens and narrow scopes to limit blast radius. If your platform supports OIDC, prefer that instead.

Step 3: Fetch Secrets

Infisical gives you two ways to access secrets in OpenTofu, and the right choice depends on your OpenTofu version and your security requirements.

Comparison: Ephemeral Resources vs. Data Sources

Ephemeral ResourcesData SourcesCLI InjectionEnv Variables
Secrets in state?NoYesNoDepends
Version requiredOpenTofu v1.11+AnyAnyAny
Centralized RBACYesYesPartialNo
Audit trailYesYesYesNo
ComplexityLowLowMediumLow
Best forProduction, sensitive credsLegacy versions, non sensitive configWrapper scripts, migrationLocal dev only

Option 1: Ephemeral Resources (Recommended, OpenTofu v1.11+)

Ephemeral resources are the preferred approach. They fetch secrets at runtime and do not write them to the state file. This is critical for highly sensitive credentials like database passwords and API keys:

# Fetch database credentials without persisting to state
ephemeral "infisical_secret" "db_credentials" {
  name         = "DB_CREDENTIALS"
  env_slug     = "prod"
  workspace_id = var.infisical_workspace_id
  folder_path  = "/database"
}
# Use them to configure a downstream provider
provider "postgresql" {
  host     = data.aws_db_instance.main.address
  port     = data.aws_db_instance.main.port
  username = jsondecode(ephemeral.infisical_secret.db_credentials.value)["username"]
  password = jsondecode(ephemeral.infisical_secret.db_credentials.value)["password"]
}

After tofu apply completes, the credentials are gone. They exist only in memory during execution and are never written to disk as part of the state file.

Version note: Ephemeral resources require OpenTofu v1.11 or later. If you are on an older version, use data sources (Option 2) and encrypt your state backend.

Option 2: Data Sources (Any OpenTofu Version)

For teams on older OpenTofu versions or for non sensitive configuration values, standard data sources work:

# Fetch all secrets in a folder
data "infisical_secrets" "api_secrets" {
  env_slug     = "dev"
  workspace_id = var.infisical_workspace_id
  folder_path  = "/api"
}
# Reference individual values
resource "aws_db_instance" "main" {
  engine         = "postgres"
  instance_class = "db.t3.medium"
  username       = data.infisical_secrets.api_secrets.secrets["DB_USER"].value
  password       = data.infisical_secrets.api_secrets.secrets["DB_PASS"].value
}

Warning: Data source values are stored in the state file. If you use this approach, make sure your state backend is encrypted (S3 with server side encryption, GCS with default encryption, or OpenTofu's native state encryption) and access controlled.

Worked Example: Provisioning an AWS RDS Instance

Here is a complete, production oriented example showing how to provision an AWS RDS instance with credentials managed by Infisical. This uses ephemeral resources and OIDC authentication.

Directory Structure

infra/
  main.tf          # Provider declarations and data sources
  database.tf      # RDS resource definition
  variables.tf     # Input variables (no secrets here)
  outputs.tf       # Non-sensitive outputs only
  backend.tf       # Remote state configuration

backend.tf

terraform {
  backend "s3" {
    bucket         = "myco-terraform-state"
    key            = "prod/database/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

variables.tf

variable "infisical_workspace_id" {
  description = "Infisical workspace ID for this project"
  type        = string
}

variable "environment" {
  description = "Deployment environment (dev, staging, prod)"
  type        = string
  default     = "prod"
}

variable "infisical_identity_id" {
  description = "Machine identity ID for OIDC auth"
  type        = string
}

main.tf

terraform {
  required_providers {
    infisical = {
      source  = "infisical/infisical"
      version = "~> 0.16"  # Check registry for latest
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "infisical" {
  auth = {
    oidc = {
      identity_id = var.infisical_identity_id
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# Fetch database credentials ephemerally
ephemeral "infisical_secret" "db_creds" {
  name         = "RDS_CREDENTIALS"
  env_slug     = var.environment
  workspace_id = var.infisical_workspace_id
  folder_path  = "/database"
}

database.tf

resource "aws_db_instance" "main" {
  identifier     = "myapp-${var.environment}"
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = "db.t3.medium"

  allocated_storage     = 50
  max_allocated_storage = 200

  db_name  = "myapp"
  username = jsondecode(ephemeral.infisical_secret.db_creds.value)["username"]
  password = jsondecode(ephemeral.infisical_secret.db_creds.value)["password"]

  vpc_security_group_ids = [aws_security_group.db.id]
  db_subnet_group_name   = aws_db_subnet_group.db.name

  storage_encrypted   = true
  skip_final_snapshot = false

  tags = {
    Environment = var.environment
    ManagedBy   = "opentofu"
  }
}

Notice that no credentials appear anywhere in the configuration files, the variables, or (because of ephemeral resources) the state file. The credentials exist only in memory during the tofu apply execution. An engineer reviewing this code in a pull request sees no secrets, and neither does anyone with access to the state bucket.

Running This in CI/CD: GitHub Actions Example

Here is a GitHub Actions workflow that runs the above configuration using OIDC authentication. No stored secrets are needed for Infisical access:

name: Deploy Database Infrastructure
on:
  push:
    branches: [main]
    paths: ["infra/database/**"]

permissions:
  id-token: write   # Required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup OpenTofu
        uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: "1.11.0"

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/opentofu-deploy
          aws-region: us-east-1

      - name: OpenTofu Init
        run: tofu init
        working-directory: infra/database

      - name: OpenTofu Plan
        run: tofu plan -out=tfplan
        working-directory: infra/database
        env:
          TF_VAR_infisical_workspace_id: ${{ vars.INFISICAL_WORKSPACE_ID }}
          TF_VAR_infisical_identity_id: ${{ vars.INFISICAL_IDENTITY_ID }}
          TF_VAR_environment: prod

      - name: OpenTofu Apply
        run: tofu apply tfplan
        working-directory: infra/database

The key detail here is the permissions block: id-token: write enables the GitHub Actions runner to issue an OIDC token, which the Infisical provider exchanges for access. The INFISICAL_WORKSPACE_ID and INFISICAL_IDENTITY_ID are non sensitive identifiers stored as GitHub Actions variables (not secrets), because they do not grant access on their own.

How This Compares to a Vault Based Workflow

Many OpenTofu users previously used Terraform with HashiCorp Vault. If you are evaluating whether to continue with Vault or adopt Infisical, here is what the practical differences look like.

Vault: What the Integration Requires

Vault's Terraform provider works, but the surrounding infrastructure is significant. You need a running Vault cluster (typically deployed with Raft consensus, which requires at least three nodes for high availability).

Someone on your team needs to understand Vault's seal/unseal process, token lifecycle, and policy language. The Terraform provider configuration looks straightforward, but behind it sits a system that most organizations staff with one to three dedicated engineers.

Beyond operations, Vault is a set of building blocks. It provides key value storage and an API. Workflows like secret rotation, approval gates, environment separation, and audit dashboards are things you build yourself, often with custom scripts, GitOps processes, and YAML based change management.

Read more: HashiCorp Vault Alternatives

Infisical: What Changes

Infisical's provider integration is functionally similar (declare a provider, fetch secrets, use them in resources), but the supporting infrastructure is different. It runs on Postgres, which most teams already know how to operate and scale. There is no Raft cluster to manage, no seal/unseal ceremony, and no proprietary consensus protocol.

More importantly, the workflows that Vault leaves to you (environment based secret organization, approval workflows, access request flows, and audit logging) come built in. This is the distinction between a toolkit you build on and a product you use.

Migrating from Vault? If you are running Vault today, you do not have to switch everything at once. Infisical can run alongside Vault as you migrate project by project. The provider is self contained, so you can use both in the same OpenTofu configuration during a transition.

Read more: Infisical vs HashiCorp Vault

Beyond Secrets: What Else Infisical Handles

The integration shown above covers the most common use case: injecting secrets into OpenTofu runs. But secrets management is one piece of a broader operational challenge.

For teams managing infrastructure at scale, the adjacent problems include certificate lifecycle management (issuing, renewing, and discovering TLS certificates, which is growing more urgent as certificate lifetimes shrink to 47 days by 2029), privileged access management for SSH sessions, and secrets scanning across repositories to catch leaked credentials before they become incidents.

Infisical handles all of these as part of a single platform. Whether that breadth matters to you depends on your current stack. If you are already running separate tools for secrets, certificates, and privileged access, consolidating reduces vendor count and eliminates the custom orchestration between them. If you are only solving for secrets today, it is useful to know the platform grows with you.

For a deeper look: The Infisical integration guide for Terraform covers additional provider options and configuration details. Since OpenTofu is a fork, the documentation applies directly.

Operational Best Practices for This Integration

These are specific to running Infisical with OpenTofu, not generic secrets management advice.

  • Pin your provider version. The Infisical provider is actively developed. Pin to a specific version in required_providers and upgrade deliberately, testing in a staging workspace first.
  • Handle fetch failures gracefully. If the Infisical provider cannot reach the server during tofu plan, the plan will fail. In CI/CD, make sure your pipeline surfaces this as a clear error rather than proceeding with empty values. Consider adding a health check step before running tofu init.
  • Scope machine identities narrowly. Create separate machine identities for each project or environment. A single identity with access to all secrets in all environments defeats the purpose of centralized access control.
  • Use folder paths for organization. Infisical's folder structure maps well to OpenTofu workspace and module boundaries. A pattern like /project-name/environment/component keeps secrets discoverable without over sharing access.
  • Encrypt state regardless of approach. Even with ephemeral resources, your state file contains other infrastructure metadata. Always use an encrypted remote backend (S3 with KMS, GCS with CMEK, or OpenTofu's native state encryption) and restrict access to it.
  • Rotate the Universal Auth secret. If you are using Universal Auth instead of OIDC, treat the client_secret like any other credential: rotate it on a schedule, use Infisical's built in rotation policies, and ensure your CI/CD pipeline picks up the new value automatically.
  • Separate read and write identities. Your CI/CD pipeline that runs tofu apply only needs read access to secrets. Reserve write access for a separate identity used by your secrets management workflow. This limits what a compromised pipeline can do.

Getting Started

The integration between Infisical and OpenTofu is a provider declaration, an authentication method, and a secret fetch. The code examples in this guide are production ready patterns you can adapt to your infrastructure.

If you are currently managing secrets with .tfvars files, environment variables, or a Vault setup that has outgrown your team's capacity to maintain it, this is a practical path forward. Start with a single project, validate the workflow in CI/CD, and expand from there.

The Infisical documentation covers the full provider API, including options not shown here. For questions or help with migration, the Infisical community is active and responsive.

Ashwin Punj avatar

Ashwin Punj

Solutions Engineer, Infisical

Starting with Infisical is simple, fast, and free.