Skip to main content
OCIDevOps & IaCintermediate

Terraform on OCI

Configure the OCI Terraform provider, manage compartments as code, and deploy infrastructure with best practices.

CloudToolStack Team20 min readPublished Mar 14, 2026

Prerequisites

  • Basic Terraform knowledge (providers, resources, state)
  • OCI account with API key authentication configured

Infrastructure as Code with Terraform on OCI

Terraform is the most widely used infrastructure as code (IaC) tool for Oracle Cloud Infrastructure. The OCI Terraform provider, maintained by Oracle, supports every OCI service and provides comprehensive resource coverage. Using Terraform to manage your OCI infrastructure brings repeatability, version control, collaboration, and the ability to review infrastructure changes before they are applied, just like code reviews.

OCI also offers Resource Manager, a managed Terraform service built directly into OCI. Resource Manager handles state file management, execution environments, and drift detection without requiring you to set up a Terraform backend or CI/CD pipeline. However, many teams prefer standard Terraform workflows for flexibility and tool chain integration.

This guide covers OCI Terraform provider setup, authentication methods, compartment patterns, common resource configurations, state management, modules, and production best practices for managing OCI infrastructure as code.

OCI Resource Manager

OCI Resource Manager is a free, fully managed Terraform service. It stores state files in OCI, runs plan and apply operations on Oracle's infrastructure, supports GitOps workflows, and includes drift detection. If you do not want to manage Terraform state files or build your own CI/CD pipeline, Resource Manager is an excellent option. Everything in this guide applies to both standard Terraform and Resource Manager, as they use the same provider and HCL syntax.

Provider Configuration

The OCI Terraform provider needs authentication credentials to interact with OCI APIs. There are three authentication methods: API key authentication (using your OCI config file), instance principal (for compute instances running Terraform), and Resource Manager (no explicit authentication needed since it runs within OCI).

hcl
# Required Terraform configuration
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    oci = {
      source  = "oracle/oci"
      version = "~> 6.0"
    }
  }
}

# Provider configuration using API key authentication
# Reads from ~/.oci/config by default
provider "oci" {
  region = var.region
  # If using the default config file and profile:
  # No additional configuration needed

  # Explicit configuration (if not using config file):
  # tenancy_ocid     = var.tenancy_ocid
  # user_ocid        = var.user_ocid
  # fingerprint      = var.fingerprint
  # private_key_path = var.private_key_path
}

# Provider using instance principal (for compute instances / Resource Manager)
provider "oci" {
  alias  = "instance_principal"
  region = var.region
  auth   = "InstancePrincipal"
}

# Variables for common configuration
variable "region" {
  description = "OCI region"
  type        = string
  default     = "us-ashburn-1"
}

variable "compartment_ocid" {
  description = "Target compartment OCID"
  type        = string
}

variable "tenancy_ocid" {
  description = "Tenancy OCID"
  type        = string
}

Authentication Setup

bash
# Option 1: Use existing OCI CLI config (recommended for local dev)
# Terraform reads ~/.oci/config automatically
# Ensure you have run: oci setup config

# Option 2: Set environment variables
export TF_VAR_tenancy_ocid="ocid1.tenancy.oc1..aaa..."
export TF_VAR_user_ocid="ocid1.user.oc1..aaa..."
export TF_VAR_fingerprint="aa:bb:cc:dd:..."
export TF_VAR_private_key_path="~/.oci/oci_api_key.pem"
export TF_VAR_region="us-ashburn-1"
export TF_VAR_compartment_ocid="ocid1.compartment.oc1..aaa..."

# Option 3: Instance Principal (for compute instances)
# Create a dynamic group matching the instance
# Create a policy granting the dynamic group manage permissions
# Set auth = "InstancePrincipal" in the provider block

# Initialize Terraform
terraform init

# Verify the provider is working
terraform plan

Never Commit Credentials

Never store API keys, private keys, or OCIDs in your Terraform files or commit them to version control. Use environment variables, the OCI config file, or a secrets manager. Add *.pem, *.key, and terraform.tfvarsto your .gitignore file. For team environments, use OCI Vault to store sensitive values and reference them in Terraform using data sources.

Compartment Patterns

A well-designed compartment structure is the foundation of your Terraform configuration. Create compartments as code to ensure consistency across environments and enable reproducible deployments.

hcl
# Compartment hierarchy for an enterprise deployment
resource "oci_identity_compartment" "network" {
  compartment_id = var.tenancy_ocid
  name           = "Network"
  description    = "Shared networking resources"

  freeform_tags = {
    "ManagedBy" = "terraform"
    "Team"      = "platform"
  }
}

resource "oci_identity_compartment" "security" {
  compartment_id = var.tenancy_ocid
  name           = "Security"
  description    = "Security services, vaults, and keys"

  freeform_tags = {
    "ManagedBy" = "terraform"
    "Team"      = "security"
  }
}

resource "oci_identity_compartment" "production" {
  compartment_id = var.tenancy_ocid
  name           = "Production"
  description    = "Production workloads"

  freeform_tags = {
    "ManagedBy" = "terraform"
    "Team"      = "platform"
  }
}

resource "oci_identity_compartment" "app_a" {
  compartment_id = oci_identity_compartment.production.id
  name           = "AppA"
  description    = "Application A production resources"

  freeform_tags = {
    "ManagedBy"   = "terraform"
    "Application" = "app-a"
  }
}

# Use compartment OCIDs in other resources
resource "oci_core_vcn" "production_vcn" {
  compartment_id = oci_identity_compartment.network.id
  cidr_blocks    = ["10.0.0.0/16"]
  display_name   = "production-vcn"
  dns_label      = "prodvcn"
}

Networking Resources

Terraform excels at managing OCI networking because VCN configurations involve many interconnected resources that must be created in the correct order. Terraform handles dependency ordering automatically based on resource references.

hcl
# Complete VCN with public and private subnets
resource "oci_core_vcn" "main" {
  compartment_id = oci_identity_compartment.network.id
  cidr_blocks    = ["10.0.0.0/16"]
  display_name   = "main-vcn"
  dns_label      = "mainvcn"
}

# Internet Gateway
resource "oci_core_internet_gateway" "main" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "main-igw"
  enabled        = true
}

# NAT Gateway
resource "oci_core_nat_gateway" "main" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "main-nat-gw"
}

# Service Gateway
data "oci_core_services" "all_services" {
  filter {
    name   = "name"
    values = ["All .* Services In Oracle Services Network"]
    regex  = true
  }
}

resource "oci_core_service_gateway" "main" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "main-sgw"

  services {
    service_id = data.oci_core_services.all_services.services[0].id
  }
}

# Public route table
resource "oci_core_route_table" "public" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "public-rt"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.main.id
  }
}

# Private route table
resource "oci_core_route_table" "private" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "private-rt"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_nat_gateway.main.id
  }

  route_rules {
    destination       = data.oci_core_services.all_services.services[0].cidr_block
    destination_type  = "SERVICE_CIDR_BLOCK"
    network_entity_id = oci_core_service_gateway.main.id
  }
}

# Public subnet
resource "oci_core_subnet" "public" {
  compartment_id             = oci_identity_compartment.network.id
  vcn_id                     = oci_core_vcn.main.id
  cidr_block                 = "10.0.1.0/24"
  display_name               = "public-subnet"
  dns_label                  = "pubsub"
  prohibit_public_ip_on_vnic = false
  route_table_id             = oci_core_route_table.public.id
  security_list_ids          = [oci_core_vcn.main.default_security_list_id]
}

# Private subnet
resource "oci_core_subnet" "private" {
  compartment_id             = oci_identity_compartment.network.id
  vcn_id                     = oci_core_vcn.main.id
  cidr_block                 = "10.0.2.0/24"
  display_name               = "private-subnet"
  dns_label                  = "privsub"
  prohibit_public_ip_on_vnic = true
  route_table_id             = oci_core_route_table.private.id
  security_list_ids          = [oci_core_vcn.main.default_security_list_id]
}

# Network Security Group
resource "oci_core_network_security_group" "web" {
  compartment_id = oci_identity_compartment.network.id
  vcn_id         = oci_core_vcn.main.id
  display_name   = "web-nsg"
}

resource "oci_core_network_security_group_security_rule" "web_https" {
  network_security_group_id = oci_core_network_security_group.web.id
  direction                 = "INGRESS"
  protocol                  = "6" # TCP
  source                    = "0.0.0.0/0"
  source_type               = "CIDR_BLOCK"
  description               = "Allow HTTPS from anywhere"

  tcp_options {
    destination_port_range {
      min = 443
      max = 443
    }
  }
}

Compute Resources

Terraform makes compute provisioning repeatable and version-controlled. The following patterns cover common compute scenarios including flex shapes, cloud-init, and instance pools.

hcl
# Data source: Get the latest Oracle Linux image
data "oci_core_images" "oracle_linux" {
  compartment_id           = var.compartment_ocid
  operating_system         = "Oracle Linux"
  operating_system_version = "8"
  shape                    = "VM.Standard.E4.Flex"
  sort_by                  = "TIMECREATED"
  sort_order               = "DESC"
}

# Data source: Get availability domains
data "oci_identity_availability_domains" "ads" {
  compartment_id = var.tenancy_ocid
}

# Compute instance with flex shape
resource "oci_core_instance" "app_server" {
  compartment_id      = oci_identity_compartment.app_a.id
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
  display_name        = "app-server-1"
  shape               = "VM.Standard.E4.Flex"

  shape_config {
    ocpus         = 2
    memory_in_gbs = 16
  }

  source_details {
    source_type = "image"
    source_id   = data.oci_core_images.oracle_linux.images[0].id
    boot_volume_size_in_gbs = 100
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.private.id
    assign_public_ip = false
    nsg_ids          = [oci_core_network_security_group.web.id]
    hostname_label   = "app-server-1"
  }

  metadata = {
    ssh_authorized_keys = file("~/.ssh/id_rsa.pub")
    user_data           = base64encode(file("cloud-init.sh"))
  }

  freeform_tags = {
    "ManagedBy"   = "terraform"
    "Environment" = "production"
    "Application" = "app-a"
  }

  lifecycle {
    ignore_changes = [
      source_details[0].source_id, # Ignore image updates
    ]
  }
}

# Output the private IP
output "app_server_private_ip" {
  value = oci_core_instance.app_server.private_ip
}

Autonomous Database

hcl
# Autonomous Transaction Processing database
resource "oci_database_autonomous_database" "app_db" {
  compartment_id           = oci_identity_compartment.app_a.id
  db_name                  = "appdb"
  display_name             = "Application Database"
  db_workload              = "OLTP"
  compute_model            = "ECPU"
  compute_count            = 4
  data_storage_size_in_tbs = 1
  admin_password           = var.db_admin_password
  license_model            = "LICENSE_INCLUDED"

  # Auto-scaling
  is_auto_scaling_enabled              = true
  is_auto_scaling_for_storage_enabled  = false

  # Private endpoint
  subnet_id  = oci_core_subnet.private.id
  nsg_ids    = [oci_core_network_security_group.db.id]

  # Connection type
  is_mtls_connection_required = false

  # Always Free (set to true for free tier)
  is_free_tier = false

  freeform_tags = {
    "ManagedBy"   = "terraform"
    "Environment" = "production"
  }
}

# Get connection strings
output "db_connection_url" {
  value = oci_database_autonomous_database.app_db.connection_urls[0].sql_dev_web_url
}

output "db_connection_strings" {
  value     = oci_database_autonomous_database.app_db.connection_strings
  sensitive = true
}

# Sensitive variable for password
variable "db_admin_password" {
  description = "Admin password for Autonomous Database"
  type        = string
  sensitive   = true

  validation {
    condition     = length(var.db_admin_password) >= 12
    error_message = "Password must be at least 12 characters."
  }
}

State Management

Terraform state files track the mapping between your configuration and real OCI resources. For team environments, state must be stored remotely so all team members work with the same state. OCI provides two excellent options: OCI Object Storage with S3 compatibility, and OCI Resource Manager (which manages state internally).

hcl
# Backend: OCI Object Storage (S3 compatible)
terraform {
  backend "s3" {
    bucket                      = "terraform-state"
    key                         = "production/infrastructure.tfstate"
    region                      = "us-ashburn-1"
    endpoint                    = "https://<namespace>.compat.objectstorage.us-ashburn-1.oraclecloud.com"
    skip_region_validation      = true
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_requesting_account_id  = true
    use_path_style              = true
    # Access key and secret key set via environment variables:
    # AWS_ACCESS_KEY_ID=<oci-customer-secret-key-access-key>
    # AWS_SECRET_ACCESS_KEY=<oci-customer-secret-key-secret>
  }
}

# Alternative: Backend using HTTP with OCI Resource Manager
# Resource Manager handles state automatically when you create a stack
# No backend configuration needed

Enable State File Versioning

Always enable versioning on the Object Storage bucket that holds your Terraform state files. This allows you to recover from state corruption or accidental deletion by restoring a previous version. Additionally, enable retention rules to prevent accidental deletion of the state bucket itself. State files may contain sensitive data (like database passwords), so restrict bucket access to only the Terraform service account and administrators.

Terraform Modules

Modules are reusable packages of Terraform configuration that encapsulate common patterns. For OCI, Oracle provides official Terraform modules in theoracle-terraform-modules GitHub organization. You can also create your own modules for patterns specific to your organization.

hcl
# Use Oracle's official VCN module
module "vcn" {
  source  = "oracle-terraform-modules/vcn/oci"
  version = "3.6.0"

  compartment_id = var.compartment_ocid
  region         = var.region

  vcn_name      = "production-vcn"
  vcn_dns_label = "prodvcn"
  vcn_cidrs     = ["10.0.0.0/16"]

  create_internet_gateway = true
  create_nat_gateway      = true
  create_service_gateway  = true

  freeform_tags = {
    "ManagedBy" = "terraform"
  }
}

# Use Oracle's OKE module
module "oke" {
  source  = "oracle-terraform-modules/oke/oci"
  version = "5.0.0"

  compartment_id = var.compartment_ocid
  region         = var.region
  tenancy_id     = var.tenancy_ocid

  kubernetes_version = "v1.30.1"
  cluster_name       = "production-cluster"
  cluster_type       = "enhanced"

  vcn_id = module.vcn.vcn_id

  # Node pool configuration
  node_pools = {
    general = {
      shape            = "VM.Standard.E4.Flex"
      ocpus            = 2
      memory           = 16
      size             = 3
      boot_volume_size = 50
    }
    arm = {
      shape            = "VM.Standard.A1.Flex"
      ocpus            = 4
      memory           = 24
      size             = 2
      boot_volume_size = 50
    }
  }
}

# Create your own reusable module
# modules/oci-web-app/main.tf
# Encapsulate: compute + LB + NSG + DNS into a single module call

Data Sources

OCI Terraform data sources let you query existing resources and use their attributes in your configuration. This is essential for referencing resources created outside of Terraform, looking up dynamic values (like the latest image ID), and cross-referencing between Terraform configurations.

hcl
# Get availability domains
data "oci_identity_availability_domains" "ads" {
  compartment_id = var.tenancy_ocid
}

# Get fault domains
data "oci_identity_fault_domains" "fds" {
  compartment_id      = var.tenancy_ocid
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
}

# Get the latest Oracle Linux image for a specific shape
data "oci_core_images" "ol8" {
  compartment_id           = var.compartment_ocid
  operating_system         = "Oracle Linux"
  operating_system_version = "8"
  shape                    = "VM.Standard.E4.Flex"
  sort_by                  = "TIMECREATED"
  sort_order               = "DESC"
}

# Get the Object Storage namespace
data "oci_objectstorage_namespace" "ns" {
  compartment_id = var.tenancy_ocid
}

# Look up an existing compartment by name
data "oci_identity_compartments" "network" {
  compartment_id = var.tenancy_ocid

  filter {
    name   = "name"
    values = ["Network"]
  }
}

# Get the Oracle Services Network CIDR for Service Gateway
data "oci_core_services" "all_services" {
  filter {
    name   = "name"
    values = ["All .* Services In Oracle Services Network"]
    regex  = true
  }
}

# Use data sources in resources
output "namespace" {
  value = data.oci_objectstorage_namespace.ns.namespace
}

output "latest_image_id" {
  value = data.oci_core_images.ol8.images[0].id
}

IAM Policies with Terraform

hcl
# Create groups and policies
resource "oci_identity_group" "developers" {
  compartment_id = var.tenancy_ocid
  name           = "Developers"
  description    = "Application developers"
}

resource "oci_identity_group" "network_admins" {
  compartment_id = var.tenancy_ocid
  name           = "NetworkAdmins"
  description    = "Network administrators"
}

# Developer policy
resource "oci_identity_policy" "developer_policy" {
  compartment_id = var.tenancy_ocid
  name           = "developer-policy"
  description    = "Permissions for developers"

  statements = [
    "Allow group Developers to manage instance-family in compartment Production:AppA",
    "Allow group Developers to manage object-family in compartment Production:AppA",
    "Allow group Developers to use virtual-network-family in compartment Network",
    "Allow group Developers to read all-resources in compartment Production:AppA",
  ]
}

# Dynamic group for compute instances
resource "oci_identity_dynamic_group" "app_instances" {
  compartment_id = var.tenancy_ocid
  name           = "AppA-Instances"
  description    = "All instances in AppA compartment"
  matching_rule  = "All {instance.compartment.id = '${oci_identity_compartment.app_a.id}'}"
}

# Policy for dynamic group
resource "oci_identity_policy" "instance_policy" {
  compartment_id = var.tenancy_ocid
  name           = "app-instance-policy"
  description    = "Allow app instances to access secrets and object storage"

  statements = [
    "Allow dynamic-group AppA-Instances to read secret-family in compartment Security",
    "Allow dynamic-group AppA-Instances to manage objects in compartment Production:AppA",
  ]
}

Best Practices

AreaRecommendation
StructureSeparate environments into different state files and directories. Use modules for reuse.
StateUse remote state (S3-compatible Object Storage). Enable versioning. Lock state files.
SecurityNever commit credentials. Use instance principals in CI/CD. Mark sensitive variables.
VariablesUse terraform.tfvars per environment. Validate inputs with variable validation blocks.
NamingUse consistent naming conventions. Include environment and purpose in display names.
LifecycleUse prevent_destroy on critical resources. Use ignore_changes for auto-managed attributes.
CI/CDRun terraform plan in PRs. Apply only from main branch. Use Resource Manager for managed execution.
TaggingTag every resource with ManagedBy=terraform for tracking. Use defined tags for cost allocation.
bash
# Common Terraform workflow for OCI
# 1. Initialize (download provider, configure backend)
terraform init

# 2. Format code
terraform fmt -recursive

# 3. Validate configuration
terraform validate

# 4. Plan changes (review before applying)
terraform plan -out=plan.tfplan

# 5. Apply changes
terraform apply plan.tfplan

# 6. Import existing resources into state
terraform import oci_core_vcn.main <vcn-ocid>

# 7. Destroy resources (use with caution!)
terraform plan -destroy
terraform destroy

# 8. Check for drift (compare state to actual resources)
terraform plan -refresh-only

# 9. Manage workspaces for multiple environments
terraform workspace new staging
terraform workspace select production
terraform workspace list

terraform destroy Is Permanent

The terraform destroy command will delete all resources managed by the configuration. In OCI, some resources have dependencies that must be removed in order (e.g., you cannot delete a VCN with active subnets). Terraform handles dependency ordering, but complex configurations with circular dependencies or external references may require manual intervention. Always run terraform plan -destroy first to review what will be deleted, and never run destroy against production without explicit authorization and a verified backup strategy.

Getting Started with Oracle CloudOCI IAM, Compartments & PoliciesOCI VCN Networking Deep Dive

Key Takeaways

  1. 1The OCI Terraform provider supports API key, instance principal, and resource principal authentication.
  2. 2Compartment OCIDs are the foundation of all OCI Terraform resource configurations.
  3. 3Data sources let you query existing OCI resources like availability domains and images.
  4. 4OCI Resource Manager provides managed Terraform-as-a-Service within the OCI console.

Frequently Asked Questions

How do I authenticate the OCI Terraform provider?
The most common method is API key authentication using a config file at ~/.oci/config that contains your tenancy OCID, user OCID, fingerprint, key file path, and region. For CI/CD pipelines, you can use environment variables (TF_VAR_tenancy_ocid, etc.) or instance principal authentication on OCI compute instances. Resource principal auth is available for OCI Functions and other managed services.
Should I use Terraform or OCI Resource Manager?
Use standard Terraform CLI for maximum flexibility, multi-cloud support, and CI/CD integration. Use OCI Resource Manager when you want a managed Terraform service within the OCI console with built-in state management, drift detection, and no CLI setup. Resource Manager uses the same Terraform configuration files, so you can switch between them easily.

Written by CloudToolStack Team

Cloud engineers and architects with hands-on experience across AWS, Azure, and GCP. We write guides based on real-world production patterns, not just documentation rewrites.

Disclaimer: This guide is for educational purposes. Cloud services change frequently; always refer to official documentation for the latest information. AWS, Azure, and GCP are trademarks of their respective owners.