LAB 50 ‐ create VCN, 1 subnet using terraform - DevWithNehaCloud/Oracle-Cloud-Infrastructure-wiki GitHub Wiki
Swiggy Dev Spoke Network Planning
Requirement Analysis
The objective of this activity is to design and deploy a Spoke Network in OCI using the Hub-and-Spoke Architecture Model. The Hub VCN is already available, and a new Spoke VCN needs to be created to host application workloads in a secure and scalable manner.
| Component | Requirement |
|---|---|
| Architecture | Implement a Hub-and-Spoke Network Model in OCI |
| Existing Setup | Hub VCN already exists and is managed separately |
| New Requirement | Create a dedicated Spoke VCN for the Dev environment |
| VCN Type | Private application network (Spoke Network) |
| Compartment | Swiggy-Dev-Network |
| VCN Name | spoke-dev-vcn |
| VCN CIDR | 10.0.1.0/24 |
| CIDR Planning Constraint | CIDR must not overlap with the Hub VCN CIDR |
| Reason for CIDR Selection | Hub VCN already uses 10.0.0.0/24; therefore, Spoke VCN uses 10.0.1.0/24 to avoid overlapping and routing conflicts |
| Subnet Type | Private Application Subnet |
| Subnet Name | app-subnet |
| Subnet CIDR | 10.0.1.0/26 |
| Current Capacity Requirement | Support approximately 60 application instances |
| Future Planning | Reserve IP space for two future subnets (30 IPs and 15–20 IPs respectively) |
| Route Table | Create Route Table but keep Route Rules empty for now |
| Gateway Requirement | No Internet Gateway, NAT Gateway, Service Gateway, or DRG at this stage |
| Security Requirement | Allow SSH (TCP Port 22) for testing and administration |
| Ingress Rule | Allow SSH (TCP 22) only from trusted internal networks: Hub VCN (10.0.0.0/24) and Spoke VCN (10.0.1.0/24) |
| Egress Rule | Default outbound access |
| Tagging Requirement | Use Defined Tags for governance and cost tracking |
| Naming Standard | Follow meaningful and consistent naming conventions |
| Deployment Method | Terraform Advanced Modular Approach |
| Terraform Design | Reusable Module + Environment-specific Configuration |
| Scalability Goal | Network should support future subnet expansion without redesign |
Expected Outcome
At the end of the implementation, the following OCI resources should be created:
- One Spoke VCN (
spoke-dev-vcn) - One Private Application Subnet (
app-subnet) - One Route Table (without Route Rules)
- One Security List (with SSH access for testing)
- Defined Tags for resource governance and cost tracking
- Modular Terraform code that can be reused across multiple environments
Objective
Before implementing the Hub-and-Spoke architecture using Terraform, proper network planning was performed to ensure:
- No CIDR overlap between Hub and Spoke networks
- Future scalability
- Clean IP address management
- Industry-standard network design
- Easy troubleshooting and routing
Initial Requirement
The requirement was to create a new Spoke VCN for application workloads.
IP Requirements
| Network Segment | Required IPs |
|---|---|
| App Subnet | 60 |
| Future Subnet 1 | 30 |
| Future Subnet 2 | 15-20 |
| Total | ~110 |
Existing Hub Network
The Hub VCN was already created.
Hub VCN
CIDR : 10.0.0.0/24
Hub Subnet Design
Hub VCN
10.0.0.0/24
256 IPs
│
├── Public Subnet
│ 10.0.0.0/25
│ 128 IPs
│
└── Private Subnet
10.0.0.128/25
128 IPs
Enterprise Address Space (Umbrella CIDR)
The organization reserved:
10.0.0.0/16
This serves as the enterprise address space from which all future VCNs will be allocated.
10.0.0.0/16
│
├── Hub VCN
├── Dev VCN
├── Test VCN
├── UAT VCN
├── Prod VCN
└── Future VCNs
Why We Did NOT Use 10.0.0.0/24 Again
Initially it might appear logical to use:
Spoke VCN = 10.0.0.0/24
However, the Hub VCN is already using:
10.0.0.0/24
This creates CIDR overlap.
CIDR Overlap Example
Hub
10.0.0.50
Spoke
10.0.0.50
Now both networks can contain the same IP address.
The router cannot uniquely identify the destination network.
This results in:
- Routing ambiguity
- Communication failures
- IP conflicts
- Hub-Spoke connectivity issues
This condition is known as:
CIDR Overlapping
Industry Rule
Hub and Spoke VCNs must never have overlapping CIDR ranges.
Incorrect
Hub VCN = 10.0.0.0/24
Spoke VCN = 10.0.0.0/24
❌ CIDR Overlap
Correct
Hub VCN = 10.0.0.0/24
Spoke VCN = 10.0.1.0/24
✅ No Overlap
Why We Selected 10.0.1.0/24
The Hub VCN already consumed:
10.0.0.0/24
The next logical non-overlapping network block inside the enterprise CIDR space is:
10.0.1.0/24
This keeps the addressing plan clean and scalable.
Final VCN Design
Enterprise CIDR
10.0.0.0/16
│
├── Hub VCN
│ 10.0.0.0/24
│
├── Dev Spoke VCN
│ 10.0.1.0/24
│
├── Test Spoke VCN
│ 10.0.2.0/24
│
├── UAT Spoke VCN
│ 10.0.3.0/24
│
└── Prod Spoke VCN
10.0.4.0/24
Why We Chose /24
The total requirement was approximately:
60 + 30 + 20 = 110 IPs
Available CIDR options:
| CIDR | Total IPs |
|---|---|
| /25 | 128 |
| /24 | 256 |
| /23 | 512 |
/25
128 IPs
Possible, but leaves very little room for future expansion.
/23
512 IPs
Too large for the current requirement.
/24
256 IPs
Provides sufficient space while keeping the network manageable.
Therefore:
10.0.1.0/24
was selected.
Subnet Planning
App Subnet
Requirement:
60 IPs
CIDR options:
| CIDR | IPs |
|---|---|
| /27 | 32 |
| /26 | 64 |
Selected:
10.0.1.0/26
Provides:
64 IPs
Future Subnet 1
Requirement:
30 IPs
Selected:
10.0.1.64/27
Provides:
32 IPs
Future Subnet 2
Requirement:
15-20 IPs
Possible options:
Option A
/28
Provides:
16 IPs
Risk of running out of addresses.
Option B
/27
Provides:
32 IPs
Offers future growth.
Selected:
10.0.1.96/27
Final Network Layout
Dev Spoke VCN
10.0.1.0/24
256 IPs
│
├── App Subnet
│ 10.0.1.0/26
│ 64 IPs
│
├── Future Subnet 1
│ 10.0.1.64/27
│ 32 IPs
│
├── Future Subnet 2
│ 10.0.1.96/27
│ 32 IPs
│
└── Reserved For Future Growth
10.0.1.128 - 10.0.1.255
Design Justification
Why did I choose 10.0.1.0/24 for the Spoke VCN?
The Hub VCN was already using 10.0.0.0/24. To avoid CIDR overlap and maintain a clean addressing scheme within the enterprise address space (10.0.0.0/16), I selected the next available non-overlapping block, 10.0.1.0/24, for the Dev Spoke VCN. The /24 range provides sufficient address capacity for the current application subnet requirements while also allowing future subnet expansion.
Key Takeaways
- Never create overlapping VCN CIDRs.
- Always plan VCNs from a larger enterprise CIDR block.
- Use sequential CIDR allocation for easier management.
- Size subnets based on current and future requirements.
- Leave sufficient address space for future growth.
- CIDR planning should be completed before Terraform implementation.
Enterprise Address Space
The organization reserved the CIDR block 10.0.0.0/16 for future network expansion.
All Hub and Spoke VCNs are allocated from this address space.
Where is 10.0.0.0/16 configured?
It is not configured as a resource in OCI. It is part of the network planning and address management strategy. Individual VCN CIDRs such as 10.0.0.0/24 and 10.0.1.0/24 are carved out from the planned enterprise address space 10.0.0.0/16.
Network Planning Methodology
Before implementing any cloud network, it is important to follow a structured planning approach to ensure scalability, security, and efficient resource utilization.
1. Gather Requirements and Constraints
- Understand business, application, security, and connectivity requirements.
- Identify organizational standards, compliance requirements, and existing network dependencies.
- Determine current and future growth expectations.
2. Assess Available IP Address Space
- Review the organization's existing IP addressing strategy.
- Identify available address ranges for cloud deployment.
- Ensure sufficient address space is reserved for future workloads and environments.
3. Select an Appropriate VCN CIDR Block
- Choose a CIDR range that does not overlap with existing on-premises networks, other cloud environments, or future planned networks.
- Consider long-term expansion requirements when selecting the CIDR block.
4. Design Subnets Using VLSM
VLSM (Variable Length Subnet Masking) allows network administrators to create subnets of different sizes based on actual host requirements rather than allocating the same subnet size everywhere.
Benefits of VLSM
- Optimizes IP address utilization.
- Reduces IP address wastage.
- Allows subnet sizing based on workload requirements.
- Preserves address space for future expansion.
- Improves overall network scalability.
Example
| Subnet | Required Hosts | CIDR | Available IPs |
|---|---|---|---|
| App Subnet | 60 | /26 | 64 |
| DB Subnet | 30 | /27 | 32 |
| Management Subnet | 15 | /28 | 16 |
Instead of allocating equal-sized subnets, VLSM assigns subnet sizes according to actual requirements, resulting in more efficient IP utilization.
5. Allocate Subnet CIDRs Strategically
- Assign CIDR ranges to public and private subnets.
- Keep sufficient unallocated space for future subnet creation.
- Reserve address space for future applications, environments, and growth.
6. Plan Network Components
Design and configure the required networking resources:
- Route Tables
- Security Lists
- Network Security Groups (NSGs)
- Internet Gateway (IGW)
- NAT Gateway
- Service Gateway
- Dynamic Routing Gateway (DRG)
- Load Balancers (if required)
Define traffic flow and security boundaries between network segments.
7. Validate the Network Design
Before implementation, verify that:
- No CIDR overlap exists.
- IP addresses are efficiently utilized.
- Routing paths are properly configured.
- Security controls meet requirements.
- The design supports scalability and high availability.
- Future expansion can be accommodated without major redesign.
Key Objective
A planning-first approach helps create a secure, scalable, and maintainable cloud network architecture. Proper upfront design minimizes future network rework, prevents IP addressing conflicts, optimizes IP address utilization through VLSM, and aligns with enterprise cloud networking best practices.
Terraform Structure
Terraform Directory Structure
Swiggy_Infrastructure
├── dev
│ ├── provider.tf
│ ├── versions.tf
│ ├── network.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── outputs.tf
│
└── network_module
├── main.tf
├── variables.tf
└── outputs.tf
Directory Purpose
| Directory/File | Purpose |
|---|---|
| dev | Environment-specific configuration |
| provider.tf | OCI Provider configuration |
| versions.tf | Terraform and provider version constraints |
| network.tf | Module invocation using for_each |
| variables.tf | Input variable definitions |
| terraform.tfvars | Environment-specific values |
| outputs.tf | Module outputs |
| network_module/main.tf | Creates VCN, Route Table, Security List, and Subnet |
| network_module/variables.tf | Module input variables |
| network_module/outputs.tf | Module output values |
dev/provider.tf
provider "oci" {
auth = "APIKey"
tenancy_ocid = "ocid1.tenancy.oc1..aaaaaaaacxrxza63ajbfdhvukzuxpv4y3oksxh256migpqx6lazpbbejuuga"
user_ocid = "ocid1.user.oc1..aaaaaaaa5m4rn7uj5exypqxsz54tmkqctcuzd7o4ehy2f35flcdzznncw77a"
private_key_path = "./privkey.pem"
fingerprint = "4c:bb:0e:8b:6e:b1:df:51:59:bf:c6:e3:60:2c:d6:29"
region = "us-ashburn-1"
}
dev/variables.tf
variable "compartment_id" {
type = string
}
variable "network_map" {
type = map(object({
vcn_name = string
vcn_cidr = string
subnet_name = string
subnet_cidr = string
route_table_name = string
security_list_name = string
ssh_source_1 = string
ssh_source_2 = string
}))
}
dev/terraform.tfvars
compartment_id = "ocid1.compartment.xxxxxxxxxxxxxxxxxxxxxxxxx"
network_map = {
spoke-app-network = {
vcn_name = "spoke-dev-vcn"
vcn_cidr = "10.0.0.0/24"
subnet_name = "spoke-dev-app-subnet"
subnet_cidr = "10.0.0.0/26"
route_table_name = "spoke-dev-route-table"
security_list_name = "spoke-dev-security-list"
ssh_source_1 = "10.0.0.0/24"
ssh_source_2 = "10.0.1.0/24"
}
}
dev/network.tf
module "network_module" {
for_each = var.network_map
source = "../network_module"
compartment_id = var.compartment_id
vcn_name = each.value.vcn_name
vcn_cidr = each.value.vcn_cidr
subnet_name = each.value.subnet_name
subnet_cidr = each.value.subnet_cidr
route_table_name = each.value.route_table_name
security_list_name = each.value.security_list_name
ssh_source_1 = each.value.ssh_source_1
ssh_source_2 = each.value.ssh_source_2
}
dev/outputs.tf
output "network_details" {
value = {
for k, v in module.network_module :
k => {
vcn_id = v.vcn_id
subnet_id = v.subnet_id
route_table_id = v.route_table_id
security_list_id = v.security_list_id
}
}
}
network_module/variables.tf
variable "compartment_id" {
type = string
}
variable "vcn_name" {
type = string
}
variable "vcn_cidr" {
type = string
}
variable "subnet_name" {
type = string
}
variable "subnet_cidr" {
type = string
}
variable "route_table_name" {
type = string
}
variable "security_list_name" {
type = string
}
variable "ssh_source_1" {
description = "Hub VCN CIDR"
type = string
}
variable "ssh_source_2" {
description = "Spoke VCN CIDR"
type = string
}
network_module/main.tf
VCN
resource "oci_core_vcn" "spoke_vcn" {
compartment_id = var.compartment_id
cidr_blocks = [
var.vcn_cidr
]
display_name = var.vcn_name
dns_label = "spokedev"
}
Route Table with no route rule
resource "oci_core_route_table" "spoke_rt" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
display_name = var.route_table_name
#route_rules = []
}
Security List
resource "oci_core_security_list" "spoke_sl" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
display_name = var.security_list_name
ingress_security_rules {
protocol = "6"
source = var.ssh_source_1
tcp_options {
min = 22
max = 22
}
}
ingress_security_rules {
protocol = "6"
source = var.ssh_source_2
tcp_options {
min = 22
max = 22
}
}
egress_security_rules {
protocol = "all"
destination = "0.0.0.0/0"
}
}
Private App Subnet
resource "oci_core_subnet" "app_subnet" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
cidr_block = var.subnet_cidr
display_name = var.subnet_name
prohibit_public_ip_on_vnic = true
route_table_id = oci_core_route_table.spoke_rt.id
security_list_ids = [
oci_core_security_list.spoke_sl.id
]
dns_label = "appsubnet"
}
network_module/outputs.tf
output "vcn_id" {
value = oci_core_vcn.spoke_vcn.id
}
output "subnet_id" {
value = oci_core_subnet.app_subnet.id
}
output "route_table_id" {
value = oci_core_route_table.spoke_rt.id
}
output "security_list_id" {
value = oci_core_security_list.spoke_sl.id
}
Terraform will create
Swiggy-Dev-Network
│
├── spoke-dev-vcn
│ CIDR: 10.0.0.0/24
│
├── spoke-dev-app-subnet
│ CIDR: 10.0.0.0/26
│
├── spoke-dev-route-table
│ Empty
│
└── spoke-dev-security-list
TCP 22 Open
If tomorrow we need to create 5 more Spoke VCNs So without modifying the code, you'll just add a new entry to terraform.tfvars
Now run the commands
1. Format
terraform fmt -recursive
2. Initialize
terraform init
3. validate
terraform validate
Expected: Success! The configuration is valid.
4. Plan
terraform plan
If everything is good then output should be
Plan: 4 to add, 0 to change, 0 to destroy.
5. Apply
terraform apply
verifying in Console
Terraform Formatting Commands
terraform fmt
Formats Terraform configuration files only in the current directory.
terraform fmt -recursive
Formats Terraform configuration files in the current directory and all nested subdirectories.
Why Use -recursive?
The -recursive option is commonly used in modular Terraform projects where code is distributed across multiple folders and modules. It ensures consistent formatting throughout the entire Terraform codebase with a single command.
Example
terraform fmt
Formats files only in the current folder.
terraform fmt -recursive
Formats files in the current folder and all subfolders.
Interview Answer
terraform fmtformats Terraform configuration files only in the current directory, whereasterraform fmt -recursiveformats Terraform files in the current directory as well as all nested subdirectories. The recursive option is commonly used in modular Terraform projects to maintain consistent formatting across the entire codebase.
Tagging Strategy
Objective
After provisioning the complete Hub-and-Spoke network infrastructure using Terraform Advanced Modular Approach, Defined Tags were implemented to support:
- Cost Tracking
- Resource Governance
- Resource Identification
- Standardized Resource Management
Defined Tag Namespace
Namespace:
CloudOps
Defined Tags:
| Tag Key | Value |
|---|---|
| Environment | Dev |
| Project | Swiggy |
Why Defined Tags?
Defined Tags were chosen instead of Free-Form Tags because:
- Centrally managed and governed
- Enforces standardization
- Supports cost tracking and reporting
- Prevents inconsistent tag values
- Recommended for enterprise environments
Tag Namespace and Tag Definition Creation
Before implementing tags through Terraform, a Defined Tag Namespace and Tag Definitions were created in OCI Console.
A tag namespace named CloudOps was created, followed by the creation of defined tag keys such as Environment and Project. These tags were later referenced in Terraform and applied consistently across all network resources for governance and cost tracking purposes.
Terraform Tagging Design
A common tagging strategy was implemented using Terraform Locals.
locals.tf
locals {
common_tags = {
"CloudOps.Environment" = "Dev"
"CloudOps.Project" = "Swiggy"
}
}
Note:- No Tag Namespace OCID or Tag Definition OCID was required during the Terraform implementation. Terraform automatically recognized the existing Defined Tag Namespace (CloudOps) and Tag Keys (Environment, Project) in OCI and applied them to the resources using the configured defined_tags block.
Why Locals?
Without locals, the same tags would need to be defined repeatedly for every resource.
Using locals provides:
- Centralized tag management
- Reusable configuration
- Reduced code duplication
- Easier maintenance
Tags are defined once and reused across all resources.
Resources Tagged
The following OCI resources were tagged:
| Resource |
|---|
| VCN |
| Subnet |
| Route Table |
| Security List |
Implementation:
defined_tags = var.defined_tags
Now attaching tag to all 4 resources VCN,RT,SL,Subnet
Module Integration
Tags were passed from the environment layer to the reusable network module.
Flow:
locals.tf
↓
network.tf
↓
network_module
↓
OCI Resources
Result
All network resources were successfully tagged with:
CloudOps.Environment = Dev
CloudOps.Project = Swiggy
Tags have been successfully attached to all four resources. Below is the screenshot for the VCN. Similar tags can also be verified on the Route Table, Security List, and Subnet resources.
This enables:
- Cost allocation by project
- Environment-wise resource identification
- Consistent governance across infrastructure
- Future scalability for Test, UAT, and Prod environments
Cost Tracking Strategy
Defined Tags are used to categorize resources for cost analysis.
Current Tags:
CloudOps.Environment = Dev
CloudOps.Project = Swiggy
Benefits:
- Track project-specific spending
- Filter resources by environment
- Simplify cost reporting
- Improve governance and accountability
Future environments such as Test, UAT, and Prod can reuse the same tagging framework.
Code Push to GitHub Repository
After completing the Terraform implementation, the entire project was pushed to the GitHub repository.
Before pushing the code, a .gitignore file was created to exclude sensitive files from version control. The following files were ignored:
touch .gitignore
vi .gitignore
copy and paste this
# Terraform State Files
*.tfstate
*.tfstate.*
# Sensitive Provider Configuration
provider.tf
# Key Information
privkey.pem
and then press esc:wq!
cat .gitignore
it will show all the files that you have ignored
provider.tf*.tfstateand*.tfstate.*privkey.pem
then run all the commands
git init
git add .
git status
git commit -m "Initial commit: Swiggy Infrastructure with Advanced Modular Terraform"
git branch -M main
git remote add origin <GitHub-Repository-URL>
git remote -v
git push -u origin main
Since the Git credential helper was already configured as store, the previously saved GitHub credentials were reused. Therefore, no additional username or Personal Access Token (PAT) was required during the push operation.
git config --global credential.helper
output should be store if the credentials are already stored
Finally, all Terraform modules, environment-specific configurations, and supporting files were successfully pushed to the GitHub repository.
Repository:
https://github.com/DevWithNehaCloud/Swiggy_Infrastructure
Code Refactoring and Module Structure Enhancement
To improve code readability, maintainability, and alignment with industry standards, the network module was refactored by separating individual resources into dedicated Terraform files.
Updated Module Structure
network_module/
├── vcn.tf
├── route_table.tf
├── security_list.tf
├── subnet.tf
├── variables.tf
└── outputs.tf
Benefits
- Improved code organization and readability
- Easier troubleshooting and maintenance
- Better scalability for future enhancements
- Resource-specific configuration separated into dedicated files
- Terraform state remained unchanged during refactoring
Validation
After refactoring, Terraform validation and plan execution were performed successfully.
terraform validate
terraform plan
Result:
No changes. Your infrastructure matches the configuration.
This confirms that only the code structure was improved and no OCI resources were modified or recreated.
Project Structure
Swiggy_Infrastructure
│
├── dev
│ ├── network.tf
│ ├── variables.tf
│ ├── locals.tf
│ └── terraform.tfvars
│
└── network_module
├── vcn.tf
├── route_table.tf
├── security_list.tf
├── subnet.tf
├── variables.tf
└── outputs.tf
Folder Description
dev
Environment-specific configuration files:
- network.tf → Calls the reusable network module.
- variables.tf → Defines input variable structure.
- locals.tf → Stores common values such as Defined Tags.
- terraform.tfvars → Provides actual values for the Dev environment.
network_module
Reusable Terraform module containing OCI network resources:
- vcn.tf → Creates the Spoke VCN.
- route_table.tf → Creates the Route Table.
- security_list.tf → Creates the Security List with ingress and egress rules.
- subnet.tf → Creates the Private Application Subnet.
- variables.tf → Defines module input variables.
- outputs.tf → Exposes resource outputs for future use.
network_module/vcn.tf
resource "oci_core_vcn" "spoke_vcn" {
compartment_id = var.compartment_id
cidr_blocks = [
var.vcn_cidr
]
display_name = var.vcn_name
dns_label = "spokedev"
defined_tags = var.defined_tags
}
network_module/route_table.tf
resource "oci_core_route_table" "spoke_rt" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
display_name = var.route_table_name
defined_tags = var.defined_tags
# As of now no route rules
}
network_module/security_list.tf
resource "oci_core_security_list" "spoke_sl" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
display_name = var.security_list_name
defined_tags = var.defined_tags
ingress_security_rules {
protocol = "6"
source = var.ssh_source_1
tcp_options {
min = 22
max = 22
}
}
ingress_security_rules {
protocol = "6"
source = var.ssh_source_2
tcp_options {
min = 22
max = 22
}
}
egress_security_rules {
protocol = "all"
destination = "0.0.0.0/0"
}
}
network_module/subnet_list.tf
resource "oci_core_subnet" "app_subnet" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.spoke_vcn.id
cidr_block = var.subnet_cidr
display_name = var.subnet_name
prohibit_public_ip_on_vnic = true
route_table_id = oci_core_route_table.spoke_rt.id
security_list_ids = [
oci_core_security_list.spoke_sl.id
]
dns_label = "appsubnet"
defined_tags = var.defined_tags
}
network_module/main.tf
delete entire content and paste this
# Resources moved to individual files
Now run all the terraform commands like fmt recursive, validate, plan. No need to run terraform apply because there is no configuration changes. Finally push these new code in github repo by running the command like git add., commit,push.
Refactored the network module by separating VCN, Route Table, Security List, and Subnet resources into dedicated Terraform files. After refactoring, terraform plan returned "No changes. Your infrastructure matches the configuration.", confirming that the code structure was improved without modifying any OCI resources.
Initially all network resources were maintained in a single main.tf file. Later, for better maintainability and industry-standard code organization, the resources were separated into dedicated files such as vcn.tf, subnet.tf, route_table.tf, and security_list.tf. No infrastructure changes were introduced during this refactoring, which was validated using Terraform Plan showing no changes.