Skip to main content
OCIDevOps & IaCintermediate

OCI Resource Manager

Manage OCI infrastructure as code with Resource Manager stacks, plan/apply jobs, drift detection, and resource discovery.

CloudToolStack Team22 min readPublished Mar 14, 2026

Prerequisites

  • Basic Terraform knowledge (resources, providers, state)
  • OCI account with Resource Manager permissions

Introduction to OCI Resource Manager

OCI Resource Manager is Oracle's managed Terraform-as-a-Service offering. It lets you provision, manage, and destroy OCI infrastructure using Terraform configuration files without installing Terraform locally, managing state files, or configuring provider credentials. Resource Manager handles state locking, execution environments, drift detection, and audit logging, all integrated with OCI IAM for access control.

The core concept in Resource Manager is a stack: a collection of Terraform configuration files (.tf) along with their associated variables, state file, and execution history. You create a stack by uploading Terraform configurations (from a ZIP file, Object Storage, Git repository, or compartment scan), then run jobs against the stack to plan, apply, or destroy infrastructure. Each job execution is recorded with full audit logs and the ability to roll back.

This guide covers creating stacks from multiple sources, managing variables and sensitive data, running plan/apply/destroy jobs, detecting and remediating configuration drift, generating stacks from existing infrastructure, and best practices for production Resource Manager workflows.

Resource Manager Pricing

OCI Resource Manager is free. There is no charge for stacks, jobs, state management, or drift detection. You pay only for the OCI resources that Resource Manager provisions (compute, storage, networking, etc.). This makes it a zero-cost alternative to managing your own Terraform backend infrastructure (state storage, locking, CI/CD runners). The Terraform version used by Resource Manager is updated regularly and currently supports Terraform 1.x.

Creating Stacks

Stacks can be created from several sources: a ZIP file containing Terraform configurations, an Object Storage bucket, a Git repository (GitHub, GitLab, Bitbucket, or OCI Code Repository), a Terraform template from the OCI Marketplace, or by scanning an existing compartment to generate configurations for resources that already exist.

Stack from a ZIP File

bash
# Prepare your Terraform configuration
mkdir my-infrastructure && cd my-infrastructure

# Create main.tf, variables.tf, outputs.tf, etc.
# (See Terraform configuration examples below)

# Package the configuration as a ZIP file
zip -r ../my-infrastructure.zip .

# Create the stack
oci resource-manager stack create \
  --compartment-id <compartment-ocid> \
  --display-name "production-infrastructure" \
  --description "Core production infrastructure stack" \
  --config-source '{
    "configSourceType": "ZIP_UPLOAD",
    "zipFileBase64Encoded": "'$(base64 -i ../my-infrastructure.zip)'"
  }' \
  --variables '{
    "tenancy_ocid": "<tenancy-ocid>",
    "compartment_ocid": "<compartment-ocid>",
    "region": "us-ashburn-1",
    "environment": "production",
    "vcn_cidr": "10.0.0.0/16"
  }' \
  --terraform-version "1.5.x"

# Verify the stack was created
oci resource-manager stack get \
  --stack-id <stack-ocid> \
  --query 'data.{"Name": "display-name", "State": "lifecycle-state", "TF Version": "terraform-version", "Created": "time-created"}' \
  --output table

Stack from a Git Repository

bash
# Create a stack from a GitHub repository
oci resource-manager stack create \
  --compartment-id <compartment-ocid> \
  --display-name "app-infrastructure" \
  --description "Application infrastructure managed via Git" \
  --config-source '{
    "configSourceType": "GIT_CONFIG_SOURCE",
    "configurationSourceProviderId": "<github-config-source-provider-ocid>",
    "repositoryUrl": "https://github.com/myorg/infrastructure.git",
    "branchName": "main",
    "workingDirectory": "environments/production"
  }' \
  --variables '{
    "environment": "production",
    "instance_count": "3",
    "instance_shape": "VM.Standard.E4.Flex"
  }'

# Create a GitHub configuration source provider
oci resource-manager configuration-source-provider create-github-access-token-provider \
  --compartment-id <compartment-ocid> \
  --display-name "github-provider" \
  --api-endpoint "https://api.github.com" \
  --access-token "<github-personal-access-token>"

# Create a GitLab configuration source provider
oci resource-manager configuration-source-provider create-gitlab-access-token-provider \
  --compartment-id <compartment-ocid> \
  --display-name "gitlab-provider" \
  --api-endpoint "https://gitlab.com" \
  --access-token "<gitlab-personal-access-token>"

# Create a stack from OCI Code Repository
oci resource-manager stack create \
  --compartment-id <compartment-ocid> \
  --display-name "devops-infrastructure" \
  --config-source '{
    "configSourceType": "DEVOPS_CONFIG_SOURCE",
    "projectId": "<devops-project-ocid>",
    "repositoryId": "<repo-ocid>",
    "branchName": "main",
    "workingDirectory": "terraform"
  }'

Git Integration Best Practices

Using Git as the configuration source for stacks is the recommended approach for teams. It provides version history, code review through pull requests, branching for environment management, and automatic updates when the repository changes. Store your Terraform configurations in a dedicated infrastructure repository with separate directories for each environment (dev, staging, production). Use Terraform modules for shared configuration patterns.

Terraform Configuration for OCI

Resource Manager uses the OCI Terraform provider with automatic authentication. You do not need to configure provider credentials in your Terraform files because Resource Manager injects them at runtime using the IAM permissions of the user or resource principal running the job.

hcl
# main.tf - Core infrastructure configuration
terraform {
  required_providers {
    oci = {
      source  = "oracle/oci"
      version = ">= 5.0.0"
    }
  }
}

# Provider configuration (credentials injected by Resource Manager)
provider "oci" {
  region = var.region
}

# Variables
variable "tenancy_ocid" { type = string }
variable "compartment_ocid" { type = string }
variable "region" { type = string }
variable "environment" { type = string }
variable "vcn_cidr" {
  type    = string
  default = "10.0.0.0/16"
}
variable "instance_shape" {
  type    = string
  default = "VM.Standard.E4.Flex"
}
variable "instance_count" {
  type    = number
  default = 2
}

# Data sources
data "oci_identity_availability_domains" "ads" {
  compartment_id = var.tenancy_ocid
}

data "oci_core_images" "oracle_linux" {
  compartment_id           = var.compartment_ocid
  operating_system         = "Oracle Linux"
  operating_system_version = "8"
  shape                    = var.instance_shape
  sort_by                  = "TIMECREATED"
  sort_order               = "DESC"
}

# VCN
resource "oci_core_vcn" "main" {
  compartment_id = var.compartment_ocid
  display_name   = "${var.environment}-vcn"
  cidr_blocks    = [var.vcn_cidr]
  dns_label      = var.environment
  freeform_tags = {
    Environment = var.environment
    ManagedBy   = "resource-manager"
  }
}

# Public Subnet
resource "oci_core_subnet" "public" {
  compartment_id    = var.compartment_ocid
  vcn_id            = oci_core_vcn.main.id
  display_name      = "${var.environment}-public-subnet"
  cidr_block        = cidrsubnet(var.vcn_cidr, 8, 1)
  dns_label         = "pub"
  route_table_id    = oci_core_route_table.public.id
  security_list_ids = [oci_core_security_list.public.id]
}

# Private Subnet
resource "oci_core_subnet" "private" {
  compartment_id             = var.compartment_ocid
  vcn_id                     = oci_core_vcn.main.id
  display_name               = "${var.environment}-private-subnet"
  cidr_block                 = cidrsubnet(var.vcn_cidr, 8, 2)
  dns_label                  = "priv"
  prohibit_public_ip_on_vnic = true
  route_table_id             = oci_core_route_table.private.id
  security_list_ids          = [oci_core_security_list.private.id]
}

# Internet Gateway
resource "oci_core_internet_gateway" "igw" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.main.id
  display_name   = "${var.environment}-igw"
  enabled        = true
}

# NAT Gateway
resource "oci_core_nat_gateway" "nat" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.main.id
  display_name   = "${var.environment}-nat"
}

# Route Tables
resource "oci_core_route_table" "public" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.main.id
  display_name   = "${var.environment}-public-rt"

  route_rules {
    destination       = "0.0.0.0/0"
    network_entity_id = oci_core_internet_gateway.igw.id
  }
}

resource "oci_core_route_table" "private" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.main.id
  display_name   = "${var.environment}-private-rt"

  route_rules {
    destination       = "0.0.0.0/0"
    network_entity_id = oci_core_nat_gateway.nat.id
  }
}

# Compute Instances
resource "oci_core_instance" "app" {
  count               = var.instance_count
  compartment_id      = var.compartment_ocid
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[count.index % length(data.oci_identity_availability_domains.ads.availability_domains)].name
  display_name        = "${var.environment}-app-${count.index + 1}"
  shape               = var.instance_shape

  shape_config {
    ocpus         = 2
    memory_in_gbs = 16
  }

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

  create_vnic_details {
    subnet_id        = oci_core_subnet.private.id
    assign_public_ip = false
  }

  freeform_tags = {
    Environment = var.environment
    Role        = "application"
    ManagedBy   = "resource-manager"
  }
}

# Outputs
output "vcn_id" {
  value = oci_core_vcn.main.id
}

output "instance_ips" {
  value = [for i in oci_core_instance.app : i.private_ip]
}

Running Jobs

Jobs are the execution units in Resource Manager. There are five job types: Plan (preview changes without applying), Apply(create or update infrastructure), Destroy (tear down all managed resources), Import State (import existing resources into the state), and Detect Drift (compare actual infrastructure to the state file).

bash
# Run a Plan job (preview changes)
oci resource-manager job create-plan-job \
  --stack-id <stack-ocid> \
  --display-name "plan-2026-03-14"

# Wait for the plan to complete
oci resource-manager job get \
  --job-id <job-ocid> \
  --query 'data.{"Status": "lifecycle-state", "Operation": operation, "Started": "time-created", "Finished": "time-finished"}' \
  --output table

# View the plan output (what will change)
oci resource-manager job get-job-logs \
  --job-id <job-ocid> \
  --query 'data[].message'

# Download the full plan output
oci resource-manager job get-job-tf-plan \
  --job-id <job-ocid> \
  --file plan-output.binary

# Run an Apply job (create/update infrastructure)
oci resource-manager job create-apply-job \
  --stack-id <stack-ocid> \
  --display-name "apply-2026-03-14" \
  --execution-plan-strategy FROM_PLAN_JOB_ID \
  --execution-plan-job-id <plan-job-ocid>

# Apply without a prior plan (auto-approve)
oci resource-manager job create-apply-job \
  --stack-id <stack-ocid> \
  --display-name "apply-auto" \
  --execution-plan-strategy AUTO_APPROVED

# Monitor job progress
oci resource-manager job get-job-logs \
  --job-id <job-ocid> \
  --query 'data[].message' \
  --sort-order ASC

# Run a Destroy job (tear down all resources)
oci resource-manager job create-destroy-job \
  --stack-id <stack-ocid> \
  --display-name "destroy-dev-env" \
  --execution-plan-strategy AUTO_APPROVED

# List all jobs for a stack
oci resource-manager job list \
  --stack-id <stack-ocid> \
  --query 'data[].{"ID": id, "Operation": operation, "Status": "lifecycle-state", "Created": "time-created"}' \
  --output table \
  --sort-by timeCreated \
  --sort-order DESC

Destroy Job Caution

A Destroy job permanently deletes all resources managed by the stack. This action cannot be undone. Always run a Plan job first to review what will be destroyed. For production stacks, add a lifecycle prevent_destroy meta-argument to critical resources in your Terraform configuration. Consider using IAM policies to restrict who can run Destroy jobs on production stacks.

Drift Detection

Configuration drift occurs when the actual state of your infrastructure differs from the expected state defined in your Terraform configuration and state file. Drift can be caused by manual changes in the OCI Console, CLI commands run outside of Resource Manager, or other automation tools modifying resources. Resource Manager's drift detection identifies these discrepancies so you can decide whether to accept the changes or reconcile them.

bash
# Run a drift detection job
oci resource-manager job create-detect-drift-job \
  --stack-id <stack-ocid> \
  --display-name "drift-check-2026-03-14"

# Wait for drift detection to complete
oci resource-manager job get \
  --job-id <drift-job-ocid> \
  --query 'data.{"Status": "lifecycle-state", "Operation": operation}'

# Get the drift detection results
oci resource-manager stack get-stack-tf-state \
  --stack-id <stack-ocid> \
  --file current-state.json

# View resource drift details
oci resource-manager stack list-resource-drift-details \
  --stack-id <stack-ocid> \
  --query 'data.items[?resourceDriftStatus != `NOT_MODIFIED`].{"Resource": "resource-name", "Type": "resource-type", "Drift": "resource-drift-status", "Details": "actual-properties"}' \
  --output table

# Reconcile drift by running a Plan + Apply to restore desired state
oci resource-manager job create-plan-job \
  --stack-id <stack-ocid> \
  --display-name "drift-reconciliation-plan"

# Or accept the drift by importing the current state
# (Updates the state file to match actual infrastructure)

Stack from Existing Compartment

One of Resource Manager's most powerful features is the ability to generate Terraform configurations from existing infrastructure. This is called "Resource Discovery" and creates a stack by scanning a compartment and generating .tf files for all supported resources it finds. This is invaluable for bringing manually created infrastructure under Terraform management.

bash
# Generate a stack from existing resources in a compartment
oci resource-manager stack create-from-compartment \
  --compartment-id <target-compartment-ocid> \
  --display-name "discovered-infrastructure" \
  --description "Generated from existing resources" \
  --terraform-version "1.5.x" \
  --tf-state-compartment-id <target-compartment-ocid> \
  --services '["core", "database", "load_balancer", "object_storage", "identity"]'

# Available services for discovery include:
# core - VCN, Subnets, Instances, Block Volumes
# database - DB Systems, Autonomous Databases
# load_balancer - Load Balancers, Backend Sets
# object_storage - Buckets
# identity - Policies, Groups, Dynamic Groups
# functions - Applications, Functions
# container_engine - OKE Clusters, Node Pools
# dns - Zones, Records
# file_storage - File Systems, Mount Targets

# Download the generated Terraform configuration
oci resource-manager stack get-stack-tf-config \
  --stack-id <stack-ocid> \
  --file discovered-config.zip

# Unzip and review the generated files
unzip discovered-config.zip -d discovered-config
ls -la discovered-config/

# The generated files include:
# - provider.tf (OCI provider configuration)
# - *.tf (resource definitions for each discovered resource)
# - vars.tf (variable declarations)
# - terraform.tfvars (variable values)
# - output.tf (resource outputs)

# Import state for the discovered resources
oci resource-manager job create-import-tf-state-job \
  --stack-id <stack-ocid> \
  --display-name "import-existing-state" \
  --tf-state-file <state-file-path>

Resource Discovery Limitations

Resource Discovery supports most but not all OCI services. Some resource attributes (like passwords and sensitive values) are not captured during discovery and must be manually added to the configuration. The generated Terraform code may need cleanup: renaming resources for clarity, extracting common patterns into modules, and parameterizing hardcoded values. Always review and test the generated code before using it for production management.

Managing Variables and Sensitive Data

Stacks support Terraform variables for parameterizing configurations. Variables can be set at stack creation time and updated later. For sensitive values like passwords and API keys, Resource Manager integrates with OCI Vault. You can also use Terraform's sensitive attribute to mark variables that should be masked in job logs.

bash
# Update stack variables
oci resource-manager stack update \
  --stack-id <stack-ocid> \
  --variables '{
    "instance_count": "4",
    "instance_shape": "VM.Standard.E4.Flex",
    "environment": "production"
  }'

# Get current stack variables
oci resource-manager stack get \
  --stack-id <stack-ocid> \
  --query 'data.variables'

# Use variable files (.tfvars) in the stack
# Include terraform.tfvars in your ZIP/Git configuration

# Reference Vault secrets in Terraform
# In your .tf file:
# data "oci_vault_secret" "db_password" {
#   secret_id = var.db_password_secret_ocid
# }
# data "oci_secrets_secretbundle" "db_password" {
#   secret_id = data.oci_vault_secret.db_password.id
# }

# Override variables for a specific job
oci resource-manager job create-plan-job \
  --stack-id <stack-ocid> \
  --display-name "plan-with-overrides" \
  --variables '{
    "instance_count": "6"
  }'

Best Practices for Production

Effective use of OCI Resource Manager in production requires disciplined workflows, proper access controls, and automation patterns.

Production Workflow Recommendations

PracticeWhyImplementation
Use Git-backed stacksVersion control, code reviewConnect stacks to Git repositories
Always plan before applyPreview changes, catch errorsUse FROM_PLAN_JOB_ID strategy
Schedule drift detectionCatch unauthorized changesWeekly drift detection jobs
Separate stacks per envBlast radius isolationdev, staging, prod stacks
Use modules for reuseConsistency, DRY principleShared modules in Git repos
Restrict destroy permissionsPrevent accidental deletionIAM policies on job operations
Tag all managed resourcesCost tracking, governanceDefault tags in provider block
bash
# IAM policy to restrict destroy operations
oci iam policy create \
  --compartment-id <tenancy-ocid> \
  --name RestrictDestroyJobs \
  --description "Only admins can run destroy jobs" \
  --statements '[
    "Allow group InfraAdmins to manage orm-stacks in compartment infrastructure",
    "Allow group InfraAdmins to manage orm-jobs in compartment infrastructure",
    "Allow group Developers to read orm-stacks in compartment infrastructure",
    "Allow group Developers to manage orm-jobs in compartment infrastructure where any {request.operation = \u0027CreatePlanJob\u0027, request.operation = \u0027CreateApplyJob\u0027}",
    "Allow group Developers to inspect orm-work-requests in compartment infrastructure"
  ]'

# Automate drift detection with a scheduled function
# Create an OCI Function that runs weekly:
# 1. Lists all stacks in the compartment
# 2. Creates a drift detection job for each
# 3. Reports any drift via Notifications

Resource Manager stacks should follow the same lifecycle as your application code. Store configurations in Git, review changes through pull requests, test in lower environments before production, and maintain clear documentation of each stack's purpose and ownership. Regularly audit stack state files for resources that are no longer needed and clean up unused stacks to reduce management overhead.

Terraform on OCIOCI IAM, Compartments & PoliciesOCI Cost Optimization Strategies

Key Takeaways

  1. 1Resource Manager is free Terraform-as-a-Service with managed state, locking, and execution.
  2. 2Stacks can be created from ZIP files, Git repositories, or by scanning existing compartments.
  3. 3Drift detection identifies differences between actual infrastructure and the Terraform state.
  4. 4Resource Discovery generates Terraform configurations from existing OCI infrastructure.

Frequently Asked Questions

Is OCI Resource Manager free?
Yes, OCI Resource Manager is completely free. There is no charge for stacks, jobs, state management, or drift detection. You pay only for the OCI resources that Resource Manager provisions. This makes it a zero-cost alternative to managing your own Terraform backend infrastructure.
Can I use Resource Manager with existing Terraform configurations?
Yes. Resource Manager uses the standard OCI Terraform provider, so existing configurations work without modification. The main difference is that provider credentials are injected automatically by Resource Manager based on IAM permissions, so you do not need to configure API keys in your provider block.

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.