# terraform-provider-sops: reading encrypted sops files inside a Terraform plan

> carlpett/terraform-provider-sops gives Terraform a data source and an ephemeral resource for files encrypted with Mozilla sops, so a secret can be decrypted during a plan instead of being committed to a repository in the clear. The plaintext handling rules, not the decryption, are the part to understand first.

**carlpett/terraform-provider-sops** — A Terraform provider for reading Mozilla sops files

- Repository: https://github.com/carlpett/terraform-provider-sops
- Stars: 614 · Forks: 82
- Language: Go
- License: MPL-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/carlpett-terraform-provider-sops

## The provider decrypts locally, which is why state becomes the risk

terraform-provider-sops is a Terraform plugin for reading files encrypted with sops. The interesting property is where the decryption happens: inside the Terraform process, at plan time, so that configuration and outputs can refer to values that are encrypted on disk. That design is the whole reason the README opens with a warning rather than a feature list. To prevent plaintext secrets from being written to disk, the README says you must set up a secure remote state backend and points at the official documentation on sensitive data in state, with the ephemeral block form offered as the alternative. A data source that returns a secret will record that secret in state unless you use the ephemeral form, so the provider is really two decisions in one package: whether you want sops files in your repository, and whether you are willing to let Terraform write the decrypted value into its state backend.

## data.sops_file reads one encrypted file and exposes a map

The primary data source is `sops_file`, which takes a `source_file` path and returns the decrypted content. The README's example assumes Terraform 0.13 or newer, and older versions are covered by a separate legacy usage document. The block declares the provider through the registry source `carlpett/sops`:

```hcl
data "sops_file" "demo-secret" {
  source_file = "demo-secret.enc.json"
}
```

The decrypted payload comes back in two shapes. `data` is a map, so a flat key is read as `data.sops_file.demo-secret.data["password"]` and a nested one as `data.sops_file.demo-secret.data["db.password"]`, where the dot is part of the flattened key. `raw` holds the document as a string, which is what you pass to `jsondecode` or `yamldecode` when you want real nested objects instead of flattened map keys. The choice matters more than it looks: the flattened map form is easier to index but loses the distinction between a key that literally contains a dot and a nested path, while the decoded form preserves the structure of the original document.

## input_type is what makes a non-JSON file readable

Sops can encrypt a whole file rather than selected keys, in which case the provider needs to be told how to interpret the result. That is what `input_type` does. The README shows the raw case, where the whole document is one opaque value:

```hcl
data "sops_file" "some-file" {
  source_file = "secret-data.txt"
  input_type = "raw"
}
```

The `raw` output is then consumed as a single string. The same idea carries into the second data source, `sops_external`, which exists for files that are not on the local filesystem, such as content fetched by another provider. `input_type` is required there, and the README example feeds it the contents of a `local_file` data source with the type set to `yaml`, after which values are read from `data.sops_external.demo-secret.data.hello`. That requirement is the difference between the two data sources: `sops_external` decrypts a string you already have, `sops_file` reads a path and decrypts what it finds.

## The ephemeral resource keeps plaintext out of the state file

Newer Terraform changed the shape of this problem. The README describes an ephemeral resource form that works with Terraform v1.11 and later and with the SOPS provider v1.3.0 and later, and the effect is that the contents of the secret file are not saved in Terraform state at all. The block looks like this:

```hcl
ephemeral "sops_file" "secrets" {
  source_file = "demo-secret.enc.json"
}
```

Because ephemeral values never land in state, they can be referenced in `write-only` arguments, which is the mechanism the README pairs with it. The example pushes the decrypted JSON into an AWS Systems Manager parameter:

```hcl
resource "aws_ssm_parameter" "sops_secrets" {
  name             = "my-secrets"
  type             = "SecureString"
  value_wo         = jsonencode(ephemeral.sops_file.secrets.raw)
  value_wo_version = 1
}
```

The `value_wo_version` argument is required by the write-only contract so that a changed secret produces a new version instead of an ambiguous diff. If you take one thing from this provider, take this one: the data source form is legacy-shaped, and the ephemeral form is what makes a secret-in-file workflow defensible.

## CI has to supply the sops key context that the provider does not manage

Running this in a pipeline is the part teams get wrong, and the README is unusually direct about it: for CI, the same variables or context that sops uses locally must be provided in the runtime, and the provider does not manage the required values. That is the whole contract. The provider decrypts, it does not hold, rotate, or fetch keys. So a pipeline needs the age recipient, the PGP key, or the cloud KMS credentials that match how the file was encrypted, and that credential handling lives outside this project entirely. The consequence for planning is that a pipeline which lacks the key will fail at plan time rather than at apply time, which is where you want it, but it also means the same provider configuration is not portable between environments unless the key material is provisioned separately in each.

## Moving off the old provider address needs a state rewrite

Providers used to live under a namespace that predates the registry split, and this project documents the transition in detail because the state file records the old address. The README says a prerequisite is removing the data source block from the previous provider entry in `terraform.state`, and gives the command that does it:

```bash
terraform state replace-provider registry.terraform.io/-/sops registry.terraform.io/carlpett/sops
```

If you skip it, the README shows the error you get, starting with a lookup for the latest version of `-/sops` and ending with the registry reporting that it has no provider named `registry.terraform.io/-/sops`. This is a one-time cost for anyone who started with the older provider, and a non-issue for a fresh adoption, but it is the kind of thing that turns a small upgrade into an afternoon.

## Building the provider locally and where the licence leaves you

The provider is a Go module, `github.com/carlpett/terraform-provider-sops`, on Go 1.25.8, built on `github.com/getsops/sops/v3` at version 3.12.2 and the HashiCorp plugin framework, `terraform-plugin-framework` v1.18.0. The repository also contains a `sops` package directory, a `templates` directory used to generate registry documentation, `examples` split into data sources and ephemeral resources, and a `test` directory. Development is described as easiest with `make build` and `make test`, and the PGP key used for the encrypted test fixtures sits in `test/testing-key.pgp` and can be imported with `gpg --import test/testing-key.pgp`, which is a useful detail because it tells you the acceptance tests exercise real sops encryption rather than mocks. Documentation for the registry is generated with `make generate-documentation`. The licence is MPL-2.0, which is file-level copyleft and matters if you fork the provider. The last push was on 2026-04-24, and the newest release listed is v1.4.1 from 2026-03-19 after v1.4.0 on 2026-03-04, so the release line is recent even though commits are not.

## Conclusion

This provider is the right fit when your Terraform configuration already references secrets from files and you want those files to stay encrypted at rest in version control, provided your state backend is remote and protected. It is the wrong fit if your secrets live in a secrets manager and you would rather read them from there at apply time, since the provider decrypts locally and expects the sops key context to exist in the running process. Before adopting, decide between the data source and the ephemeral resource form, because the data source writes decrypted values into state and the README is explicit that a secure remote state backend is mandatory, then check the version constraint you pin against the sops library version the provider vendors, currently sops v3.12.2.

## FAQ

### How do I install the terraform-provider-sops?

For Terraform 0.13 and later, declare the provider in a `required_providers` block with the source `carlpett/sops` and a version constraint, and Terraform downloads it from the registry. Older Terraform versions are covered by the legacy usage documentation in the repository.

### Does terraform-provider-sops write decrypted secrets into my Terraform state?

With the data source form it does, and the README states you must use a secure remote state backend to prevent plaintext secrets from being written to disk. With the ephemeral resource form, available with Terraform v1.11 and provider v1.3.0 or later, the secret file contents are not saved in state and can be used in write-only arguments.

### What is the difference between sops_file and sops_external?

`sops_file` reads an encrypted file from a local path given in `source_file`, while `sops_external` decrypts content you supply directly, such as the output of a `local_file` data source. `sops_external` requires the `input_type` argument, and `input_type` is also how you read a whole file rather than selected keys.

### How do I run terraform-provider-sops in CI?

The README states that the same variables or context that sops uses locally must be provided in the runtime, and that the provider does not manage the required values. Key material therefore has to be provisioned into the pipeline separately for every environment.

### How do I migrate from the older terraform-provider-sops namespace?

You must remove the old provider entry from `terraform.state` and replace it with `terraform state replace-provider registry.terraform.io/-/sops registry.terraform.io/carlpett/sops`, otherwise the registry reports that it has no provider named `registry.terraform.io/-/sops`.

## Sources

- [carlpett/terraform-provider-sops on GitHub](https://github.com/carlpett/terraform-provider-sops)
- [Issues](https://github.com/carlpett/terraform-provider-sops/issues)
- [License: MPL-2.0](https://github.com/carlpett/terraform-provider-sops/blob/master/LICENSE)
- [README](https://github.com/carlpett/terraform-provider-sops/blob/master/README.md)
- [Releases](https://github.com/carlpett/terraform-provider-sops/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/carlpett-terraform-provider-sops
