- Blog post • 14 min read
OpenTofu Secrets Management with Infisical: A Practical Integration Guide
- Published on

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.
.tfvarsfiles, 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
.tfvarsfiles, 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/applywithout 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 planloads credentials from variables,.tfvarsfiles, or environment variables to validate your configuration. If a variable is not marked assensitive, its value can appear in console output and logs. - During apply:
tofu applyuses 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 Resources | Data Sources | CLI Injection | Env Variables | |
|---|---|---|---|---|
| Secrets in state? | No | Yes | No | Depends |
| Version required | OpenTofu v1.11+ | Any | Any | Any |
| Centralized RBAC | Yes | Yes | Partial | No |
| Audit trail | Yes | Yes | Yes | No |
| Complexity | Low | Low | Medium | Low |
| Best for | Production, sensitive creds | Legacy versions, non sensitive config | Wrapper scripts, migration | Local 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_providersand 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 runningtofu 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/componentkeeps 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_secretlike 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 applyonly 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
Solutions Engineer, Infisical
