Note:

Get Started with Terraform in Oracle Cloud Infrastructure

Introduction

Terraform is an Infrastructure as Code (IaC) tool developed by Hashicorp that allows you to define, provision, and manage your infrastructure across multiple cloud providers using the Terraform language syntax, HCL. Terraform enables you to automate the entire lifecycle of your cloud infrastructure, making it easy to build, update, and scale resources in a consistent and reproducible manner. Terraform is widely used by DevOps teams to manage cloud resources, improve infrastructure efficiency, reduce manual errors and version control cloud resources.

This tutorial helps you to start your journey into leveraging IaC for deploying and managing your cloud infrastructure.

Terraform Architecture Workflow

Terraform Architecture Workflow

Objectives

Prerequisites

Task 1: Declare the Provider

A Terraform provider refers to a plugin that is responsible for understanding and interacting with a specific type of infrastructure or service. Providers are the bridge between Terraform and the APIs of the services or platforms you want to manage. Providers enable Terraform to communicate and interact with cloud platforms effectively. In other words, Terraform provider is like a translator that helps Terraform talk to different cloud services or systems, allowing it to create, manage, and delete resources like virtual machines, databases, and networks.

Each provider typically corresponds to a specific cloud platform (for example, OCI, AWS, Azure, GCP) or infrastructure service (for example, Kubernetes, Docker).

  1. Create a folder named terraform-beginners-demo and open it in VS Code.

  2. Install HashiCorp Terraform extension in VS Code.

  3. Create the provider.tf and variables.tf files under the terraform-beginners-demo folder.

  4. Copy the following code in the provider.tf file.

    terraform {
      required_providers {
        oci = {
          source = "oracle/oci"
          version = "5.30.0"
        }
      }
    }
    
    provider "oci" {
      tenancy_ocid          = var.tenancy_id
      user_ocid             = var.user_id
      fingerprint           = var.api_fingerprint
      private_key_path      = var.api_private_key_path
      region                = var.region
    }
    

    Note: We have used the code from the official Terraform Registry page. For more information, see Oracle Cloud Infrastructure Provider and Latest Version (5.30.0).

Task 2: Create OCI Configuration

We need OCI User Oracle Cloud Identifier (OCID), Tenancy OCID, Compartment OCID to configure the provider.

  1. Log in to the OCI Console.

  2. Click the user profile icon and select My profile.

  3. To get API key pair, follow the steps:

    1. Under Resources, select API keys and click Add API key.

    2. You can either generate a new API key pair or use your existing public key to generate an API key pair. Select Generate API key pair. Make sure to download the private key and public key.

    3. Click Add. A configuration file preview box will be displayed.

      API Key Pair Creation

    4. Copy details provided on configuration file preview and save it somewhere. This will be used in the variables.tf file.

      It should look like:

      [DEFAULT]
      user=ocid1.user.oc1..xxxxxxxxxxx
      fingerprint=xx:xx:xx:xx:xx
      tenancy=ocid1.tenancy.oc1..xxxxxxxxx
      region=us-phoenix-1 # your region ID
      key_file=<path to your private keyfile> # TODO
      
    5. Click Close.

    OR

    To get API key pair, see Generate an API key configuration in OCI console.

Task 3: Configure Terraform Environment with OCI Credentials

  1. Go to the VS Code editor and create a new file named variables.tf under the terraform-beginners-demo folder and paste the following code.

    variable "api_fingerprint" {
      description = "Fingerprint of OCI API private key for Tenancy"
      type        = string
    }
    
    variable "api_private_key_path" {
      description = "Path to OCI API private key used for Tenancy"
      type        = string
    }
    
    variable "tenancy_id" {
      description = "Tenancy ID where to create resources for Tenancy"
      type        = string
    }
    
    variable "user_id" {
      description = "User ID that Terraform will use to create resources for Tenancy"
      type        = string
    }
    
    variable "region" {
      description = "OCI region where resources will be created for Tenancy"
      type        = string
    }
    
  2. Create another new file named terraform.tfvars and paste the following code. Define each variable from variables.tf using your configuration file values from Task 2.3.

    # Identity and access parameters
    api_fingerprint      = "REPLACE_BY_YOUR_FINGERPRINT" # Fingerprint of OCI API private key for Tenancy
    api_private_key_path = "~/.oci/oci_api_key.pem"      # Path to OCI API private key used for Tenancy
    region               = "us-phoenix-1"                # OCI region where resources will be created for Tenancy
    tenancy_id           = "REPLACE_YOUR_TENACY_OCID"    # Tenancy ID where to create resources
    user_id              = "REPLACE_BY_YOUR_USER_OCID"   # Path to OCI API private key used for Tenancy
    

    Note: In production environment it will be best practice to use the terraform.tfvars file to define the terraform variables to promote security and scalability. For more information, see Variable Definitions (.tfvars) Files.

Task 4: Test Terraform Connectivity with your OCI Tenancy

  1. Create a new file named data_source.tf under the terraform-beginners-demo folder and copy the following code.

    data "oci_identity_availability_domains" "ad" {
        #Required
        compartment_id = var.tenancy_id
    }
    
  2. Create new file named output.tf under the terraform-beginners-demo folder and copy the following code.

    output "list_ads" {
      value = data.oci_identity_availability_domains.ad.availability_domains
    }
    
  3. Open a new terminal in VS Code and run the following commands to run the terraform script.

    Command 1: terraform init
    
    Command 2: terraform plan #to view your deployments what is going to be created
    
    Command 3: terraform apply #then type "yes" once prompted alternatively run "apply -auto-approve
    

    Terraform Execution Workflow

    Now you should be able to see the list of availability domains and the compartment associated with them under outputs.

    OCI Availability Domains

    You have now successfully configured your system to deploy resources in your OCI tenancy within Terraform. You have passed the variables but have not made any changes to your tenancy. We will walk through a few samples to help you better understand the process of adding, chaining and destroying resources using Terraform.

Task 5: (Example) Deploy an OCI Compute Instance using Terraform

Now we will build on top of what you have created in previous tasks to deploy networking and compute resources.

  1. Create a file named networking.tf and copy the following code.

    resource "oci_core_vcn" "test_vcn" {
      count = (var.create_new_vcn) ? 1 : 0
    
      #Required
      compartment_id = var.compartment_id
    
    
      cidr_block   = var.vcn_cidr_block
      display_name = var.vcn_display_name
      dns_label    = var.vcn_dns_label
    }
    

    Note: For more information on core-vcn variable with additional configurations, see oci_core_vcn.

  2. Edit your variables.tf file and add the following variables.

    #VCN specific variables
    variable "create_new_vcn" {
      description = "Boolean variable to specify whether to create a new VCN or to reuse an existing one."
      type        = bool
    }
    
    variable "compartment_id" {
      description = "OCI compartment where resources will be created"
      type        = string
    }
    
    variable "vcn_cidr_block" {
      description = "The list of IPv4 CIDR blocks the VCN will use"
      type        = string
    }
    
    variable "vcn_display_name" {
      description = "provide a descriptive name for the VCN - this is what you will see displayed in the OCI console"
      type        = string
    }
    
    variable "vcn_dns_label" {
      description = "provide a descriptive alphanumeric name for the DNS - this is what you will see displayed in the OCI console"
      type        = string
    }
    
    variable "vcn_id" {
      description = "provide your existing VCN OCID if create_new_vcn = false"
      type = string
    }
    
    variable "private_subnet_id" {
      description = "provide existing private subnet OCID"
      type = string
    }
    
    variable "public_subnet_id" {
      description = "provide existing public subnet OCID"
      type = string
    }
    
  3. Add the new variables in terraform.tfvars and define each variable according to your tenancy configuration.

    # VCN specific variables
    create_new_vcn = true # Set this to true if you want terraform to crearte the network for you, otherwise set it to false.
    compartment_id   = "REPLACE_BY_YOUR_COMPARTMENT_OCID" # add your own compartment id where the vcn will be created 
    vcn_cidr_block   = "10.0.0.0/16"                      # The list of IPv4 CIDR blocks the VCN will use
    vcn_display_name = "terraform_vcn_example"   # provide a descriptive name for the VCN - this is what you will see displayed in the OCI console
    vcn_dns_label    = "terraformvcn"                     # provide a descriptive alphanumeric name for the DNS - this is what you will see displayed in the OCI console
    
    # Configure CIDR Blocks, Subnet(Public, Private) OCIDS for an existing VCN. #
    vcn_id            = "REPLACE_BY_YOUR_VCN_OCID"             #ADD WITH YOUR VCN OCID
    private_subnet_id = "REPLACE_BY_YOUR__PRIVATE_SUBNET_OCID" #ADD WITH YOUR PRIVATE SUBNET
    public_subnet_id  = "REPLACE_BY_YOUR_PUBLIC_SUBNET__OCID"  #AA WITH YOUR PUBLIC SUBNET
    

    Note: If you already have an existing VCN then set the create_new_vcn to equal false and modify vcn_id, private_subnet_id, and public_subnet_id variables.

  4. Create subnets for your new VCN.

    1. In the networking.tf file, add the following code to create your public and private subnets.

      resource "oci_core_subnet" "private_subnet" {
        count = (var.create_new_vcn) ? 1 : 0
      
        #Required
        cidr_block     = var.private_subnet_cidr_block
        compartment_id = var.compartment_id
        vcn_id         = oci_core_vcn.test_vcn.*.id[0]
      
        display_name               = var.private_subnet_display_name
        prohibit_public_ip_on_vnic = var.private_subnet_prohibit_public_ip_on_vnic
      }
      
      resource "oci_core_subnet" "public_subnet" {
        count = (var.create_new_vcn) ? 1 : 0
      
        #Required
        cidr_block     = var.public_subnet_cidr_block
        compartment_id = var.compartment_id
        vcn_id         = oci_core_vcn.test_vcn.*.id[0]
      
      
        display_name               = var.public_subnet_display_name
        prohibit_public_ip_on_vnic = var.public_subnet_prohibit_public_ip_on_vnic
        route_table_id             = oci_core_route_table.test_route_table.*.id[0]
      }
      

      Note: For more information on core-subnet variable with additional configurations, see oci_core_subnet.

    2. Add the new subnet variables into the variables.tf file by copying the following code.

        #Private subnet variables
      variable "private_subnet_cidr_block" {
        description = "OCI private subnet CIDR block range"
        type        = string
      }
      
      
      variable "private_subnet_display_name" {
        description = "provide a descriptive name for the private subnet - this is what you will see displayed in the OCI console"
        type        = string
      }
      
      
      variable "private_subnet_prohibit_public_ip_on_vnic" {
        description = "Allow public IP address to the VNIC"
        type        = bool
      }
      
      
      #Public subnet variables
      variable "public_subnet_cidr_block" {
        description = "OCI public subnet CIDR block range"
        type        = string
      }
      
      
      variable "public_subnet_display_name" {
        description = "provide a descriptive name for the public subnet - this is what you will see displayed in the OCI console"
        type        = string
      }
      
      
      variable "public_subnet_prohibit_public_ip_on_vnic" {
        description = "Allow public IP address to the VNIC"
        type        = bool
      }
      
    3. Declare the new subnet variables in terrform.tfvars file.

      #Private subnet variables
      private_subnet_cidr_block                 = "10.0.1.0/24"                               # OCI private subnet CIDR block range
      private_subnet_display_name               = "terraform_private_subnet_example" # provide a descriptive name for the private subnet - this is what you will see displayed in the OCI console
      private_subnet_prohibit_public_ip_on_vnic = false                                       # Allow public IP address to the VNIC
      
      #Public subnet variables
      public_subnet_cidr_block                 = "10.0.2.0/24"                              # OCI public subnet CIDR block range
      public_subnet_display_name               = "terraform_public_subnet_example" # provide a descriptive name for the public subnet - this is what you will see displayed in the OCI console
      public_subnet_prohibit_public_ip_on_vnic = false 
      
  5. Add the following code in the networking.tf file to create an internet gateway.

    resource "oci_core_internet_gateway" "test_internet_gateway" {
      count = (var.create_new_vcn) ? 1 : 0
    
      #Required
      compartment_id = var.compartment_id
      display_name   = "INTERNET_GTWFOR_${var.vcn_display_name}"
      vcn_id         = oci_core_vcn.test_vcn.*.id[0]
      #route_table_id = oci_core_route_table.test_route_table.id
    }
    
  6. Add the following code in networking.tf for the route table to manage traffic to the internet.

    resource "oci_core_route_table" "test_route_table" {
      count = (var.create_new_vcn) ? 1 : 0
    
      #Required
      compartment_id = var.compartment_id
      vcn_id         = oci_core_vcn.test_vcn.*.id[0]
    
    
      route_rules {
        #Required
        network_entity_id = oci_core_internet_gateway.test_internet_gateway.*.id[0]
    
    
        description      = "route rule internet access for ${var.vcn_display_name}"
        destination      = "0.0.0.0/0"
        destination_type = "CIDR_BLOCK"
      }
    }
    
  7. Create the Network Security Group (NSG) associated with the VCN. Copy the following code into the networking.tf file.

    resource "oci_core_network_security_group" "test_nsg" {
      count = (var.create_new_vcn) ? 1 : 0
    
      #Required
      compartment_id = var.compartment_id
      vcn_id         = oci_core_vcn.test_vcn.*.id[0]
      display_name   = "NETWORK_SECURITY_GROUP_${var.vcn_display_name}"
      freeform_tags  = { "Lab" = "Terraofm 101 Guide" }
    }
    
  8. We will now create 2 new files, compute_linux.tf and compute_windows.tf, for deploying a Linux VM and a Windows VM.

  9. In compute_linux.tf, copy the following code to create a compute instance with a Linux OS.

    resource "oci_core_instance" "test_linux_instance" {
      #Required
      count = var.create_linux_instance ? 1 : 0
    
      availability_domain = data.oci_identity_availability_domains.ad.availability_domains[0].name
      compartment_id      = var.compartment_id
    
    
      create_vnic_details {
        assign_public_ip       = "true"
        display_name           = var.instance_display_name
        nsg_ids                = []
        skip_source_dest_check = "false"
        subnet_id              = var.create_new_vcn ? oci_core_subnet.public_subnet.*.id[0] : var.public_subnet_id
      }
    
      display_name = "${var.instance_display_name}_linux"
    
      metadata = {
        ssh_authorized_keys = "${file(var.public_ssh_key)}"
      }
    
      shape = var.instance_shape
      shape_config {
    
        memory_in_gbs = var.instance_flex_memory_in_gbs
        ocpus         = var.instance_flex_ocpus
      }
    
      launch_options {
        boot_volume_type                    = "PARAVIRTUALIZED"
        firmware                            = "UEFI_64"
        is_consistent_volume_naming_enabled = "true"
        is_pv_encryption_in_transit_enabled = "true"
        network_type                        = "PARAVIRTUALIZED"
        remote_data_volume_type             = "PARAVIRTUALIZED"
    
      }
    
      source_details {
        #Required
        source_id   = var.linux_image_ocid
        source_type = "image"
    
      }
      preserve_boot_volume = false
    }
    
  10. In compute_windows.tf, copy the following code.

    resource "oci_core_instance" "test_windows_instance" {
      #Required
      count = var.create_windows_instance ? 1 : 0
    
    
      availability_domain = data.oci_identity_availability_domains.ad.availability_domains[0].name
      compartment_id      = var.compartment_id
    
    
    
      create_vnic_details {
        assign_public_ip       = "true"
        display_name           = var.instance_display_name
        nsg_ids                = []
        skip_source_dest_check = "false"
        subnet_id              = var.create_new_vcn ? oci_core_subnet.public_subnet.*.id[0] : var.public_subnet_id
    
    
      }
    
      display_name = "${var.instance_display_name}_windows"
    
      metadata = {
      }
    
      shape = var.instance_shape
      shape_config {
    
        memory_in_gbs = var.instance_flex_memory_in_gbs
        ocpus         = var.instance_flex_ocpus
      }
    
      launch_options {
        boot_volume_type                    = "PARAVIRTUALIZED"
        firmware                            = "UEFI_64"
        is_pv_encryption_in_transit_enabled = "true"
        network_type                        = "PARAVIRTUALIZED"
        remote_data_volume_type             = "PARAVIRTUALIZED"
    
      }
    
      source_details {
        #Required
        source_id   = var.windows_image_ocid
        source_type = "image"
    
      }
      preserve_boot_volume = false
    }
    
  11. Update variables.tf with the new variables for in compute_linux and compute_windows.

    #Compute variables
    variable "instance_shape" {
      description = "value"
      type        = string
    }
    
    
    variable "instance_flex_memory_in_gbs" {
      description = "(Updatable) The total amount of memory available to the instance, in gigabytes."
      type        = number
    }
    
    
    variable "instance_flex_ocpus" {
      description = "(Updatable) The total number of OCPUs available to the instance."
      type        = number
    }
    
    
    variable "instance_create_vnic_details_assign_public_ip" {
      description = "To allow compute connectivity from internet"
      type        = bool
    }
    
    
    variable "instance_display_name" {
      description = "provide a descriptive name for the compute instance - this is what you will see displayed in the OCI console"
      type        = string
    }
    
    
    variable "public_ssh_key" {
      description = "Add your public ssh key - for provisioning your compute instance"
      type        = string
    }
    
    
    variable "private_ssh_key" {
      description = "Add your private ssh key - for accessing your compute instance after creation"
      type        = string
    }
    
    
    variable "create_linux_instance" {
      description = "Boolean variable to specify whether to provision a Linux instances"
      type        = bool
    }
    
    
    variable "create_windows_instance" {
      description = "Boolean variable to specify whether to provision a Windows instances"
      type        = bool
    }
    
    
    variable "windows_image_ocid" {
      description = "OCID of the Windows image to use"
      type        = string
    }
    
    
    variable "linux_image_ocid" {
      description = "OCID of the Linux image to use"
      type        = string
    }
    
  12. Add and define the new variables in terraform.tfvars.

    #Compute variables - Make sure to select a compatible shape (e.g.: VM.Standard.E4.Flex)
    instance_shape                                = "VM.Standard.E5.Flex"                # Shape of the compute instance 
    instance_flex_memory_in_gbs                   = 16                                   # (Updatable) The total amount of memory available to the instance, in gigabytes.
    instance_flex_ocpus                           = 1                                    # (Updatable) The total number of OCPUs available to the instance.
    instance_create_vnic_details_assign_public_ip = true                                 # To allow compute connectivity from internet
    instance_display_name                         = "terraform_compute_example" # provide a descriptive name for the compute instance - this is what you will see displayed in the OCI console
    
    #SSH keys https://docs.oracle.com/en/learn/generate_ssh_keys/index.html#introduction
    public_ssh_key  = "~/cloudshellkey.pub" # Add your public ssh key
    private_ssh_key = "~/cloudshellkey"     # Add your private ssh key
    
    create_linux_instance   = true # if set to true a test linux instance will be created and false no linux instance will be deployed.
    create_windows_instance = true # # If set to true a test windows instance will be created and false no windows instance will be deployed.
    
    linux_image_ocid   = "REPLACE_BY_YOUR_REGION_LINUX_IMAGE_OCID"   # OCID for chosen image (Oracle Linux 9 example) specific to the test region (us-phoenix-1)
    windows_image_ocid = "REPLACE_BY_YOUR_REGION_WINDOWS_IMAGE_OCID" # OCID for chosen image (Windows example) specific to each region (us-phoenix-1)
    
    # Here are other image OCIDs for popular region (Ashburn, San Jose, Toronto)
    # Ashburn
    # Oracle linux image_id = ocid1.image.oc1.iad.aaaaaaaau7uaok7n5qd4nivgiyfatfdddhltmxddtfbyqg3bsg3fxk6z6aqq
    # Windows image_id = ocid1.image.oc1.iad.aaaaaaaamaaiupezxbrw6fji5ndk3jdujwhjuexcafheqjqf45g6nzyblz6a
    
    #San Jose
    # Oracle linux image_id = ocid1.image.oc1.us-sanjose-1.aaaaaaaabjixxpfouczgpcnpvgny5pcqtgjgi3nincszbfdkd2xr4jvzahua
    # Windows image_id = ocid1.image.oc1.us-sanjose-1.aaaaaaaatmjlzoqw5gzohjvygzcm5rpugomxyfho5xi6subjchoxnxo4wcfa
    
    #Toronto
    # Oracle linux image_id = ocid1.image.oc1.ca-toronto-1.aaaaaaaai6uhjrtajuuitl5hara5brnvwqvq4aebenmnbehv2cila75xbvzq
    # Windows image_id = ocid1.image.oc1.ca-toronto-1.aaaaaaaaeged3obrrmmwvyruvknszy23btvb2fqu7vn3c5azeecbj2prm64q
    
    # for other image OCIDs: https://docs.oracle.com/en-us/iaas/images/
    

    Note: Image OCIDs are specific to each region. Within the above code there are images OCIDs for popular regions and you can find Image OCIDs for all other regions here: All Image Families.

  13. In output.tf, copy the following code. This will print out the resources created in terminal.

    # Regions
    output "linux_instance_region" {
      value = oci_core_instance.test_linux_instance.*.region
    }
    
    output "windows_instance_region" {
      value = oci_core_instance.test_windows_instance.*.region
    }
    
    # Networking
    output "network_vcn_name" {
      value = oci_core_vcn.test_vcn.*.display_name
    }
    
    # Compute: Linux Test Instance
    output "output_linux_instance_display_name" {
      value = oci_core_instance.test_linux_instance.*.display_name
    }
    
    output "output_linux_instance_public_ip" {
      value = oci_core_instance.test_linux_instance.*.public_ip
    }
    
    output "output_linux_instance_state" {
      value = oci_core_instance.test_linux_instance.*.state
    }
    
    # Compute: Windows Test Instance
    output "output_windows_instance_display_name" {
      value = oci_core_instance.test_windows_instance.*.display_name
    }
    
    output "output_windows_instance_public_ip" {
      value = oci_core_instance.test_windows_instance.*.public_ip
    }
    
    output "output_windows_instance_state" {
      value = oci_core_instance.test_windows_instance.*.state
    }
    
  14. Open new terminal for terraform-example and run the following commands.

    Command 1: terraform init
    
    Command 2: terraform plan
    
    Commnad 3: terraform apply (and then respond "yes") OR terraform apply -auto-approve
    

    You should see the following output and a VCN, private subnet, and public subnet, route table, internet gateway, nsg, Linux VM and Windows VM should be created.

    Terraform Example Terminal Execution

  15. Resources have now been deployed using Terraform. Once you verify resource creation in your OCI tenancy use the following command to destroy all resources.

    Command: terraform destroy
    

Next Steps

You have now built out the connection to Terraform and learned to deploy, update and destroy resources with a simple example. To continue your Terraform journey in OCI and set yourself up for the OCI Architect Professional Certification check out Accelerate Oracle Cloud Infrastructure Architect Professional Certification with Terraform.

We welcome contributions from readers to improve and expand this tutorial. Your contributions are highly valued in improving this resource for future Terraform learners.

Acknowledgments

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.