I need to use both Terraform Cloud and remote backends with the same configuration (different stages for the same application being deployed).
For my production stages, I use Terraform Cloud, and for the development stage, I’d like to specify the backend as S3.
If I try this without a cloud block, it works fine (where acc.tfvars holds my bucket configuration):
terraform init -backend-config=acc.tfvars
Using Terraform Cloud however, requires me to have a cloud block, and can not be specified on the command line:
terraform {
cloud {}
}
I can specify the configuration like the workspace and organisation on the CLI (TF_CLOUD_ORGANIZATION=myorg TF_WORKSPACE=acc-someapp tf init), but withou that cloud block above, it will not work.
The trouble is that if I have the cloud block and try to use -backend-config as above, I get the following error:
The -backend-config=... command line option is only for state backends, and is not applicable to Terraform Cloud-based configurations
EDIT: Actually this doesn’t resolve the issue. Although init accepts the -cloud flag, plan and apply don’t, meaning you end up with the same errors about invalid flags:
Terraform doesn’t support exactly what you are trying to do here; each configuration has exactly one backend type.
The way I would typically meet your goal is to write two root modules that consist only of a backend configuration or cloud configuration and a call to a common shared module. All of the infrastructure that should be common across both (which might be all of it) belongs to the shared module.
This is analogous to writing a shared library in a general-purpose language and then calling it from two different programs.
If you don’t like that option then another possibility is to omit the backend/cloud configuration from your root module entirely and then use your automation or wrapper script to generate a backend configuration just in time before running terraform init. This is effectively what Terraform Cloud itself does when asked to run a configuration in the remote execution environment, to ensure that the Terraform process always talks to the expected workspace regardless of how the backend was configured.
Thanks @apparentlymart. I was actually half way through implementing the second option, exactly as you described.
But the first option sounds interesting. Say my current module is called app-x, would it then look like this:
Write another module for my registry called app-x-tfc with configuration for Terraform Cloud, and a module call to app-x
Another module called app-x-s3, with configuration for an S3 backend, and the same module call to app-x
Given a single repo that holds the Terraform code, wouldn’t this have the same problem, since the repo would have to have one or the other (i.e. a file calling either app-x-tfc or app-x-s3)? Or am I missing something?
If I’m following your thought process correctly, I think you are imagining something “upside-down” to what I was describing.
Using the names you used, the app-x-tfc and the app-x-s3 modules would both be the root modules in the scenario I described, and would therefore be independent of each other and each have their own backend/cloud configuration. It would be inside each of those directories separately that you’d run terraform init and then terraform apply, so that the root module contains the appropriate backend/cloud configuration.
The app-x module would then become what we call a “shared module”, which is a module that you only use by calling it from another module block. A shared module never contains a backend configuration and you never run terraform apply in its directory directly. The shared module describes all of the infrastructure that should be present for both app-x-tfc and app-x-s3, so you don’t need to duplicate any code except for the module block that calls app-x.