Repository navigation
LAB 45 ‐ Create instance using modular approach
This documentation explains the multi-environment cloud infrastructure architecture designed and deployed on Oracle Cloud Infrastructure (OCI) using a Modular Approach in Terraform.
We have provisioned four separate environments tailored to specific business and engineering needs:
- Development (Dev): For initial feature implementation and coding.
- Testing (Test/QA): For quality assurance and automated scripts.
- User Acceptance Testing (UAT/Staging): Mirroring production for final client approvals.
- Production (Prod): Live environment optimized for real-time application users.
To maintain clean code separation and reusability, we followed a structured directory pattern under the root folder compute_service. Each environment isolates its own Terraform state backend files, while leveraging a shared, reusable module for core compute resources.
Below is the directory tree of our setup:
compute_service/
├── instance_module/ # Reusable Architecture Core
│ ├── main.tf # Core OCI compute resource configurations
│ └── variables.tf # Input parameter definitions (OCPU, Memory, etc.)
├── dev/ # Development Environment
│ ├── instance.tf # Calls instance_module with Dev variables
│ ├── provider.tf # OCI provider & authentication setup
│ ├── privkey.pem # Private key for SSH access
│ ├── terraform.tfstate # Local state file for Dev
│ └── terraform.tfstate.backup
├── test/ # Testing Environment
│ ├── instance.tf # Calls instance_module with Test variables
│ ├── provider.tf # OCI provider setup
│ ├── privkey.pem # Private key for SSH access
│ ├── terraform.tfstate # Local state file for Test
│ └── terraform.tfstate.backup
├── uat/ # UAT Environment
│ ├── instance.tf # Calls instance_module with UAT variables
│ ├── provider.tf # OCI provider setup
│ ├── privkey.pem # Private key for SSH access
│ ├── terraform.tfstate # Local state file for UAT
│ └── terraform.tfstate.backup
└── prod/ # Production Environment
├── instance.tf # Calls instance_module with Prod variables
├── provider.tf # OCI provider setup
├── privkey.pem # Private key for SSH access
├── terraform.tfstate # Local state file for Prod
└── terraform.tfstate.backup
📸 Directory Structure Screenshot
At first glance, it may appear that files such as provider.tf, instance.tf and even privkey.pem are duplicated across environments.
However, this separation is intentional and follows Terraform best practices.
Each environment should operate independently without impacting other environments.
For example:
- Development (Dev) may be used for testing new changes.
- Test may be used for validation and functional testing.
- UAT may be used for user acceptance testing.
- Production (Prod) hosts the live workload.
By maintaining separate directories, each environment has its own:
- Provider configuration
- State file
- Private key
- Module invocation
- Resource lifecycle
This prevents accidental changes across environments.
The private key does not necessarily have to be different for each environment.
For example, the same OCI API key may be used in all environments:
dev/privkey.pem
test/privkey.pem
uat/privkey.pem
prod/privkey.pem
The reason for storing the key inside each environment directory is not because the key must be unique, but because each environment is designed to be self-contained.
Benefits include:
- Better organization
- Easier portability
- Environment isolation
- Simpler provider configuration
- Reduced risk of incorrect file references
Example:
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = "./privkey.pem"
region = var.region
}Since the provider uses a relative path, keeping the private key inside the same environment directory ensures that the configuration remains independent and easy to manage.
A common misconception is that Terraform executes only the files present inside the current environment directory.
For example, in the dev environment:
dev/
├── provider.tf
└── instance.tf
The instance.tf file contains a module block similar to:
module "instance" {
source = "../instance_module"
# variables
}When the following command is executed:
cd dev
terraform applyTerraform first reads all .tf files present in the dev directory:
provider.tfinstance.tf
Terraform then evaluates the module source specified inside instance.tf.
Since the module points to:
../instance_module
Terraform automatically loads and processes:
instance_module/main.tfinstance_module/variables.tf
Therefore, even though the command is executed from the dev directory, Terraform also reads and processes the files located inside the referenced module.
The execution flow is:
terraform apply
│
▼
Read dev/provider.tf
│
▼
Read dev/instance.tf
│
▼
Locate module source
(../instance_module)
│
▼
Load main.tf
Load variables.tf
│
▼
Create Execution Plan
│
▼
Provision Resources
provider "oci" {
auth = "APIKey"
tenancy_ocid = "ocid1.tenancy.oc1..aaaaaaaacxrxza63ajbfdhvukzuxpv4y3oksxh256migpqx6lazpbbejuuga"
user_ocid = "ocid1.user.oc1..aaaaaaaa5m4rn7uj5exypqxsz54tmkqctcuzd7o4ehy2f35flcdzznncw77a"
private_key_path = "/home/ngsoftlook/compute_service/dev/privkey.pem"
fingerprint = "4c:bb:0e:8b:6e:b1:df:51:59:bf:c6:e3:60:2c:d6:29"
region = "us-ashburn-1"
}
module "dev_instance_creation" {
source = "/home/ngsoftlook/compute_service/instance_module"
env_Swiggy_Finance = "dev"
env_display_name = "dev_instance"
env_memory_in_gbs = "12"
env_ocpus = "1"
env_boot_volume_size = 50
}
For the TEST, UAT, and PROD environments, we only need to update the values in the instance.tf file according to the specific requirements of each environment.
for test (instance.tf)
for uat (instance.tf)
for prod (instance.tf)
This means Terraform does not create resources directly from instance.tf.
Instead:
-
instance.tfcalls the module. - Terraform loads the module files.
- Resources defined in
main.tfare evaluated. - Variables defined in
variables.tfare processed. - Resources are created according to the module configuration.
- Clear separation between environments
- Reusable infrastructure code through modules
- Independent state management
- Easier maintenance and troubleshooting
- Reduced code duplication
- Consistent deployments across environments
- Better scalability for large Terraform projects
Although each environment contains only a small number of files such as provider.tf, instance.tf, and privkey.pem, Terraform is still able to create resources because instance.tf references the shared module.
When Terraform executes from an environment directory, it reads the local configuration files first and then automatically loads all Terraform files inside the referenced module. This approach provides environment isolation while still allowing infrastructure code to be reused across Dev, Test, UAT, and Production environments.
We have differentiated the environments by varying their Compute Shapes (OCPU & Memory) and Boot Volume Sizes inside each environment's instance.tf parameters. This balance optimizes cloud expenditure for testing while guaranteeing high availability and performance in Production.
| Environment | OCPU Count | Memory (GB) | Boot Volume Size | Purpose |
|---|---|---|---|---|
| Dev | 1 | 12 GB | 50 GB | Lightweight, active development & debugging. |
| Test | 2 | 16 GB | 50 GB | Standard QA cycles and automated unit testing. |
| UAT | 2 | 32 GB | 100 GB | High-capacity staging for client reviews. |
| Prod | 4 | 64 GB | 150 GB | Live deployment environment for production workloads. |
To deploy or update any specific environment, the process requires navigating into that environment's distinct folder before running Terraform commands. This safely isolates the terraform.tfstate logic.
Navigate to the desired environment folder (e.g., cd dev or test or uat or prod) and run initialize to fetch provider plugins and parse the shared module references.
terraform init
Step 2: Validate and Plan the Infrastructure Generate an execution plan to verify what resources will be created or altered according to that environment's customized sizing parameters.
terraform plan
Step 3: Apply the Configuration Execute the changes to provision the OCI compute instance.
terraform apply
Now, you can see that the resource address is displayed with the module path instead of the direct resource name. This indicates that the compute instance is being managed through the Terraform module. The output will show a resource address similar to:
module.dev_instance_creation.oci_core_instance.generated_oci_core_instance
This confirms that Terraform is creating and managing the instance using the modular approach.
🔍 Verification & Artifacts After successful deployment, each environment successfully maintains its unique cloud footprint tracked separately via local backend state configurations (terraform.tfstate).
📸 OCI Console Dashboards
To view all resources currently managed by Terraform, run the following command:
terraform state list
You will notice that the resource address now includes the module path, indicating that the resource is being managed through the Terraform module rather than being defined directly in the root configuration.
Since we created four separate instances (dev, test, uat, and prod) using individual modules, we can selectively destroy them one at a time by using Terraform's -target option. This allows us to remove a specific instance without affecting the other resources managed by Terraform.
In the following steps, we will destroy each instance individually and verify the changes before proceeding to the next one.
terraform destroy -target="module.dev_instance_creation"
terraform destroy -target="module.test_instance_creation"
terraform destroy -target="module.uat_instance_creation"
terraform destroy -target="module.prod_instance_creation"
Now check in console