Terraform Troubleshooting - DevWithNehaCloud/Oracle-Cloud-Infrastructure-wiki GitHub Wiki
Terraform does not run a specific .tf file.
Instead, Terraform reads and combines all .tf files present in the current working directory and treats them as a single configuration.
Suppose the current directory contains:
instance.tf
instance2.tf
network.tf
security.tf
When you run:
terraform planor
terraform applyTerraform will read all four files and process every resource defined in them.
Terraform does not execute only the file you are currently editing.
resource "oci_core_instance" "server1" {
...
}resource "oci_core_instance" "server2" {
...
}resource "oci_core_vcn" "vcn1" {
...
}When you run:
terraform planTerraform evaluates all resources together:
- server1
- server2
- vcn1
Even if you were only making changes in instance2.tf.
The directory contained two instance resources:
resource "oci_core_instance" "generated_oci_core_instance" {
...
}resource "oci_core_instance" "server" {
count = 3
...
}Terraform calculated:
generated_oci_core_instance = 1 instance
server[count=3] = 3 instances
Total = 4 instances
Therefore the plan displayed:
Plan: 4 to add, 0 to change, 0 to destroy.
If you want Terraform to process only a specific configuration:
mkdir ThreeInstanceTest
cd ThreeInstanceTestCopy only the required Terraform files into that directory.
Example:
cp ~/instance2.tf .Then run:
terraform init
terraform plan
terraform applyTerraform will process only the files present in that directory.
Terraform only reads files with the .tf extension.
Example:
mv instance.tf instance.tf.bakNow Terraform will ignore instance.tf.
When creating a new Terraform project directory, the OCI provider configuration must also be present.
Example:
provider "oci" {
auth ="APIKey"
tenancy_ocid = "<TENANCY_OCID>"
user_ocid = "<USER_OCID>"
private_key_path = "/home/ngsoftlook/privkey.pem"
fingerprint = "<FINGERPRINT>"
region = "us-ashburn-1"
}If the provider block is missing, Terraform may fail with errors such as:
Error: can not create client, bad configuration:
did not find a proper configuration for key id
- Terraform processes all
.tffiles in the current directory. - Terraform does not execute a single
.tffile independently. - Resources from multiple
.tffiles are combined into one configuration. - A new working directory requires its own provider configuration.
- Files that do not have the
.tfextension are ignored by Terraform. - Using
countallows multiple resources to be created from a single resource block. - Always review the output of
terraform planbefore runningterraform apply.
If want to create 3 instance at once then add these lines in the code
save the file and then again run terraform plan and terraform apply.
Note - in this instance case name would be - terraform-instance-1, terraform-instance-2, terraform-instance-3
If you want to change(update the name of that instances not recreate the instances)then the code would be(before the resource block)
If you want to recreate these 3 servers (in new .tf file copy entire content) and change these lines in the code
Provider configuration should always be defined in the Root Module, not inside child modules.
dev/
├── provider.tf
├── instance.tf
├── variables.tf
└── outputs.tf
instance_module/
├── main.tf
├── variables.tf
└── outputs.tf
In the above example:
-
devis the Root Module. -
instance_moduleis a Child Module. - The OCI provider configuration must be placed in the Root Module (
dev/provider.tf).
provider "oci" {
auth = "APIKey"
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
private_key_path = var.private_key_path
fingerprint = var.fingerprint
region = var.region
}When Terraform runs from a directory, it expects provider configurations to be available in the Root Module. Child modules inherit the provider configuration from the Root Module.
If the provider configuration is defined only inside a child module, Terraform may fail with errors such as:
## Provider "registry.terraform.io/hashicorp/oci" requires explicit configuration.
The provider configuration does not need to be in the same file as the resource definition. However, it should be present in the Root Module from which Terraform commands are executed.
Each environment can have its own:
- OCI region
- OCI user
- API key
- Authentication configuration
without affecting other environments.
Provider-related issues can be isolated to a specific environment.
For example:
dev/provider.tf
test/provider.tf
uat/provider.tf
prod/provider.tf
If a configuration issue occurs in Dev, it can be fixed without impacting Test, UAT, or Production.
The same module can be reused by multiple environments without modification.
module "instance" {
source = "../instance_module"
}The module remains generic and reusable because provider details are managed by the calling environment.
Terraform recommends keeping provider configurations in the root module and keeping child modules provider-agnostic whenever possible.
This makes the infrastructure code easier to maintain and reuse across multiple environments.
During implementation, provider-related issues were encountered when provider.tf existed only inside the instance_module directory.
The issue was resolved by moving the provider configuration to each environment directory (dev, test, uat, and prod) and allowing the environments to call the shared module.
This resulted in:
- Cleaner architecture
- Better environment isolation
- Easier troubleshooting
- Improved module reusability
- Compliance with Terraform best practices
Although it may seem convenient to keep provider.tf inside the shared module, Terraform works more effectively when provider configurations are defined in the root module (environment directory) and modules are used only for reusable resource definitions.
By moving provider.tf into each environment directory, the infrastructure becomes easier to manage, troubleshoot, and scale while maintaining a clean separation between environment-specific configuration and reusable Terraform code.
In our advanced modular approach, we added variables.tf and terraform.tfvars inside our dev folder. Now, we have a variables.tf file in two different places.
Here is the simplest explanation of the difference between them:
Think of this file as a "Requirement List" or an empty box.
- What it does: It tells Terraform what inputs the module needs to create an instance (for example: Name, Memory, CPU).
- Important Point: It contains NO hardcoded values. It only defines the structure. It waits for the values to come from the outside.
Think of this file as a "Bridge" or a connector.
-
What it does: It sits in the
devfolder and connects your actual environment values to the Child Module. -
How it works: It takes the real data from your
terraform.tfvarsfile and passes it down into the module block.
To make it fully clear, look at how a value like memory size flows through your files:
Here, you are simply telling Terraform that this module requires an input variable called env_memory_in_gbs to run. No fixed value (such as 12) is defined here. Instead, you only specify the variable's structure and data type, allowing the value to be provided later by the root module.
Here, you declare a variable for the Dev environment so that Terraform can accept a value from an external source, such as terraform.tfvars. This variable acts as a placeholder that receives the environment-specific value, which can then be passed to the module.
This is where you assign the actual value to the variable. Terraform automatically reads the value from terraform.tfvars and uses it during execution.
In other words, this file contains the environment-specific values that will be passed to the variables defined in the root module.
In this file, we connect the value from terraform.tfvars to the module.
First, Terraform reads the value defined in terraform.tfvars and stores it in var.dev_memory. Then, instance.tf passes that value to the module variable. This allows the module to use the memory size specified for the Dev environment without hardcoding it inside the module.
-
variables.tfinside the module: Defines the inputs required by the module. Think of it as an empty template that declares the values the module expects to receive. -
variables.tfinside the Dev folder: Acts as a bridge betweenterraform.tfvarsand the module. It accepts values fromterraform.tfvarsand passes them to the module.
With this approach, you no longer need to modify instance.tf or make changes inside the module whenever you want to update a configuration.
Instead, you can simply update the values in terraform.tfvars, and Terraform will automatically use the new settings during the next run.
This makes your code:
- Easier to manage
- More reusable
- More scalable
- Environment-specific without changing the module code
🎯 Key Takeaway Inside the Module (instance_module): Defines WHAT input is needed.
Inside the Environment (dev): Controls WHICH actual value goes into that input.
Thanks to this setup, if you want to change your server configuration in the future, you never need to touch the main code or the module folder. You only change the simple values inside terraform.tfvars!
No. Terraform does not reliably expand ~ to the user's home directory like a shell does.
❌ Avoid:
private_key_path = "~/.oci/oci_api_key.pem"Terraform may treat ~ as a literal character instead of converting it to the home directory path.
Terraform supports standard relative paths such as:
private_key_path = "./keys/oci_api_key.pem"private_key_path = "../keys/oci_api_key.pem"Where:
-
./= Current directory -
../= One directory above the current directory
terraform/
├── keys/
│ └── oci_api_key.pem
└── dev/
├── provider.tf
└── instance.tf
If provider.tf is inside the dev folder:
private_key_path = "../keys/oci_api_key.pem"Terraform will look for:
terraform/keys/oci_api_key.pem
Use path.module to build file paths relative to the current Terraform module.
private_key_path = "${path.module}/keys/oci_api_key.pem"Or:
private_key_path = "${path.module}/../keys/oci_api_key.pem"path.module returns the absolute path of the current module directory.
For example, if your Terraform module is located at:
/home/opc/terraform/dev
And you use:
private_key_path = "${path.module}/keys/oci_api_key.pem"Terraform internally resolves it to:
/home/opc/terraform/dev/keys/oci_api_key.pem
Another example:
private_key_path = "${path.module}/../keys/oci_api_key.pem"Terraform resolves it to:
/home/opc/terraform/keys/oci_api_key.pem
- Portable across environments
- More reliable than hardcoded paths
- Works consistently regardless of where Terraform is executed from
- Recommended for reusable modules
private_key_path = "/home/opc/.oci/oci_api_key.pem"or
private_key_path = "/etc/oci/oci_api_key.pem"Absolute paths are valid but may require changes when moving code between environments.
| Method | Recommendation |
|---|---|
path.module |
✅ Best Practice |
| Absolute Path | ✅ Good |
./ and ../
|
|
~ (tilde) |
❌ Avoid |
Terraform does not reliably expand the tilde (
~) like a shell. It is recommended to use absolute paths or Terraform path functions such aspath.module.
Yes. Terraform supports relative paths such as
./and../. However,path.moduleis generally preferred because it resolves paths consistently across different environments and makes modules more portable.
I created a parent folder structure:
oci-terraform-code
├── compute_service
└── identity_service
After pushing the code to GitHub, both folders were visible in the repository, but clicking on them showed no files inside.
GitHub displayed:
compute_service
identity_service
However, the actual Terraform files and subfolders were not visible.
Both folders contained their own .git directories:
compute_service/.git
identity_service/.git
Because of this, Git treated them as separate Git repositories instead of normal folders.
Verification:
git ls-filesOutput:
compute_service
identity_service
This indicated that Git was tracking the folders themselves rather than the files inside them.
ls -la compute_service
ls -la identity_serviceIf .git exists inside either folder:
compute_service/.git
identity_service/.git
remove them.
rm -rf compute_service/.git
rm -rf identity_service/.gitVerify:
ls -la compute_service
ls -la identity_serviceThe .git folder should no longer be present.
git rm --cached -r compute_service
git rm --cached -r identity_servicegit add .git ls-filesExpected output:
compute_service/dev/provider.tf
compute_service/dev/instance.tf
compute_service/instance_module/main.tf
identity_service/dev/main.tf
identity_service/compartment_module/variables.tf
git commit -m "Added complete compute_service and identity_service code"git push origin masteroci-terraform-code
├── compute_service
│ ├── dev
│ ├── test
│ ├── prod
│ ├── uat
│ └── instance_module
│
└── identity_service
├── dev
├── test
├── prod
├── uat
└── compartment_module
When combining multiple Terraform projects into a single GitHub repository:
❌ Avoid keeping separate .git directories inside project folders.
compute_service/.git
identity_service/.git
✅ Maintain only one Git repository at the parent level.
oci-terraform-code
├── compute_service
└── identity_service
This ensures Git tracks all files and folders correctly and prevents folders from appearing as empty links on GitHub.
Now that the repository structure has been organized correctly, adding new Terraform service folders is very simple.
oci-terraform-code
├── compute_service
├── identity_service
In the future, you may want to add additional Terraform services, such as:
oci-terraform-code
├── compute_service
├── identity_service
└── networking_service
├── dev
├── test
├── prod
└── subnet_module
or
oci-terraform-code
├── compute_service
├── identity_service
└── app_subnet_service
Since the parent folder (oci-terraform-code) is already a Git repository, there is no need to run git init inside any new service folder.
Navigate to the repository root:
cd ~/oci-terraform-codeCreate a new service folder:
mkdir networking_serviceOr copy an existing Terraform project:
cp -r subnet_service networking_servicegit statusExample output:
Untracked files:
networking_service/
Add all changes:
git add .Or add only the new folder:
git add networking_service/git commit -m "Added networking_service Terraform code"git push origin masterThe new folder will automatically appear in GitHub:
oci-terraform-code
├── compute_service
├── identity_service
└── networking_service
Never initialize Git inside a child service folder:
cd networking_service
git initDo not create:
networking_service/.git
Nested Git repositories can cause GitHub to display folders incorrectly and may prevent files from being tracked properly.
Maintain only one Git repository at the parent folder level:
oci-terraform-code
│
├── .git
├── compute_service
├── identity_service
└── networking_service
Only the parent folder should contain the .git directory.
As the project grows, additional Terraform services can be added using the same approach:
oci-terraform-code
├── compute_service
├── identity_service
├── networking_service
├── storage_service
├── security_service
├── monitoring_service
└── database_service
This structure is commonly used in Terraform projects, GitHub portfolios, and enterprise infrastructure repositories because it keeps services organized, scalable, and easy to manage.
git init inside individual service folders when the parent folder is already a Git repository.
For example:
cd networking_service
git initor
cd storage_service
git initDoing this creates an additional .git directory inside the service folder:
oci-terraform-code
├── .git
├── compute_service
├── identity_service
└── networking_service
└── .git
This creates a nested Git repository. As a result, Git may track only the folder reference instead of the files inside it.
Possible symptoms include:
- The folder appears on GitHub, but its contents are not visible.
- The folder behaves like an empty link.
- Terraform files inside the folder are not tracked correctly.
-
git ls-filesmay show only the folder name instead of the actual files.
To avoid this issue, maintain only one .git directory at the repository root level:
oci-terraform-code
├── .git
├── compute_service
├── identity_service
└── networking_service
This ensures all Terraform files are tracked and displayed correctly on GitHub.