
Infrastructure As Code
- 78 installs
- 16 repo stars
- Updated April 24, 2026
- acedergren/oci-agent-skills
infrastructure-as-code is a Claude Code skill that provides Oracle Cloud Infrastructure Terraform expertise for provider gotchas, state management, resource lifecycle, and Landing Zone modules.
About
This is a Claude Code skill for writing Terraform for Oracle Cloud Infrastructure. It covers terraform-provider-oci gotchas, resource-lifecycle anti-patterns, state management, authentication, and Resource Manager stacks, and it recommends official OCI Landing Zone modules over hand-written code. A developer uses it when authoring OCI Terraform or debugging provider errors. It matters because OCI provider arguments differ from AWS and Azure and hardcoded OCIDs break portability.
- terraform-provider-oci gotchas: no hardcoded OCIDs, lifecycle traps, state management
- Recommends official Oracle Landing Zone Terraform modules over hand-rolled code
- Covers Resource Manager stacks and OCI-specific provider arguments
Infrastructure As Code by the numbers
- 78 all-time installs (skills.sh)
- Ranked #596 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
infrastructure-as-code capabilities & compatibility
- Capabilities
- infrastructure as code · terraform authoring · state management · landing zone modules
- Works with
- oracle · terraform
- Use cases
- devops · ci cd
- Pricing
- Free
What infrastructure-as-code says it does
You are an OCI Terraform expert. This skill provides knowledge Claude lacks: provider-specific gotchas, state management anti-patterns, resource lifecycle traps, and OCI-specific IaC operational knowl
NEVER hardcode OCIDs in Terraform (breaks portability)
npx skills add https://github.com/acedergren/oci-agent-skills --skill infrastructure-as-codeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 78 |
|---|---|
| repo stars | ★ 16 |
| Last updated | April 24, 2026 |
| Repository | acedergren/oci-agent-skills ↗ |
What it does
Write and debug Terraform for OCI, manage state, and use Resource Manager stacks and Landing Zone modules.
Who is it for?
Engineers writing or debugging Terraform for Oracle Cloud Infrastructure.
Skip if: Teams not using Terraform on OCI.
When should I use this skill?
Writing Terraform for OCI, troubleshooting provider errors, managing state, or implementing Resource Manager stacks.
What you get
Portable, maintainable OCI Terraform that avoids hardcoded OCIDs and leverages official Landing Zone modules.
By the numbers
- References official OCI Landing Zone Terraform module version ~> 2.0
Files
OCI Infrastructure as Code - Expert Knowledge
🏗️ IMPORTANT: Use OCI Landing Zone Terraform Modules
Do NOT Reinvent the Wheel
❌ WRONG Approach:
# Writing Terraform from scratch for every resource
resource "oci_identity_compartment" "prod" { ... }
resource "oci_core_vcn" "main" { ... }
resource "oci_identity_policy" "policies" { ... }
# Result: Unmaintainable, inconsistent, no governance✅ RIGHT Approach: Use Official OCI Landing Zone Terraform Modules
# Use official OCI Landing Zone modules
module "landing_zone" {
source = "oracle-terraform-modules/landing-zone/oci"
version = "~> 2.0"
# Infrastructure configuration
compartments_configuration = { ... }
network_configuration = { ... }
security_configuration = { ... }
}Why Use Landing Zone Modules:
- ✅ Battle-tested: Thousands of OCI customers
- ✅ Compliant: CIS OCI Foundations Benchmark aligned
- ✅ Maintained: Oracle updates for API changes
- ✅ Comprehensive: Includes IAM, networking, security, logging
- ✅ Reusable: Consistent patterns across environments
Official Resources:
When to Write Custom Terraform (this skill's guidance):
- Application-specific resources not covered by landing zone
- Extending landing zone modules
- Special requirements not in reference architecture
---
⚠️ OCI CLI/API Knowledge Gap
You don't know OCI CLI commands or OCI API structure.
Your training data has limited and outdated knowledge of:
- OCI Terraform provider syntax (updates frequently)
- OCI API endpoints and resource schemas
- terraform-provider-oci specific arguments and data sources
- Resource Manager stack operations
- Latest provider features and breaking changes
When OCI operations are needed: 1. Use exact Terraform examples from this skill's references 2. Do NOT guess OCI provider resource arguments 3. Do NOT assume AWS/Azure Terraform patterns work in OCI 4. Reference landing-zones skill for module usage
What you DO know:
- General Terraform concepts and HCL syntax
- State management principles
- Infrastructure as Code best practices
This skill bridges the gap by providing current OCI-specific Terraform patterns and gotchas.
---
You are an OCI Terraform expert. This skill provides knowledge Claude lacks: provider-specific gotchas, state management anti-patterns, resource lifecycle traps, and OCI-specific IaC operational knowledge.
NEVER Do This
❌ NEVER hardcode OCIDs in Terraform (breaks portability)
# WRONG - breaks when moving between regions/compartments
resource "oci_core_instance" "web" {
compartment_id = "ocid1.compartment.oc1..aaaaaa..." # Hardcoded!
subnet_id = "ocid1.subnet.oc1.phx.bbbbbb..." # Hardcoded!
}
# RIGHT - use variables or data sources
resource "oci_core_instance" "web" {
compartment_id = var.compartment_ocid
subnet_id = data.oci_core_subnet.existing.id
}❌ NEVER use `preserve_boot_volume = true` in dev/test (cost trap)
# WRONG - orphans boot volumes when instance destroyed ($50+/month per instance)
resource "oci_core_instance" "dev" {
preserve_boot_volume = true # Default behavior!
}
# RIGHT - explicit cleanup in dev/test
resource "oci_core_instance" "dev" {
preserve_boot_volume = false
}Cost impact: Dev team with 10 test instances × $5/volume/month = $50/month wasted on orphaned volumes
❌ NEVER forget `lifecycle` blocks for critical resources
# WRONG - accidental destroy can delete production database
resource "oci_database_autonomous_database" "prod" {
# No protection!
}
# RIGHT - prevent accidental destruction
resource "oci_database_autonomous_database" "prod" {
lifecycle {
prevent_destroy = true
ignore_changes = [defined_tags] # Ignore tag changes from console
}
}❌ NEVER mix regional and AD-specific resources (portability trap)
# WRONG - hardcoded AD breaks multi-region deployment
resource "oci_core_instance" "web" {
availability_domain = "fMgC:US-ASHBURN-AD-1" # Tenant-specific!
}
# RIGHT - query AD dynamically
data "oci_identity_availability_domains" "ads" {
compartment_id = var.tenancy_ocid
}
resource "oci_core_instance" "web" {
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
}❌ NEVER store state file in local filesystem for teams
# WRONG - no locking, no collaboration
terraform {
backend "local" {}
}
# RIGHT - use OCI Object Storage with locking
terraform {
backend "s3" {
bucket = "terraform-state"
key = "prod/terraform.tfstate"
region = "us-phoenix-1"
endpoint = "https://namespace.compat.objectstorage.us-phoenix-1.oraclecloud.com"
skip_region_validation = true
skip_credentials_validation = true
skip_metadata_api_check = true
use_path_style = true
}
}❌ NEVER use `count` for resources that shouldn't be replaced on reorder
# WRONG - reordering list recreates ALL resources
resource "oci_core_instance" "web" {
count = length(var.instance_names)
display_name = var.instance_names[count.index]
}
# If instance_names changes from ["web1", "web2", "web3"] to ["web0", "web1", "web2", "web3"]
# Terraform RECREATES all instances!
# RIGHT - use for_each with stable keys
resource "oci_core_instance" "web" {
for_each = toset(var.instance_names)
display_name = each.value
}OCI Provider Gotchas
Authentication Hierarchy (Often Confusing)
Provider authentication precedence: 1. Explicit provider block credentials 2. TF_VAR_* environment variables 3. ~/.oci/config file (DEFAULT profile) 4. Instance Principal (if auth = "InstancePrincipal")
Common mistake: Setting environment variables but provider block overrides them silently.
Instance Principal for Terraform on OCI Compute
# In provider.tf
provider "oci" {
auth = "InstancePrincipal"
region = var.region
}
# Dynamic group matching rule:
# "ALL {instance.compartment.id = '<compartment-ocid>'}"
# IAM policy:
# "Allow dynamic-group terraform-instances to manage all-resources in tenancy"Critical: Instance must be in dynamic group BEFORE Terraform runs, or authentication fails with cryptic error: "authorization failed or requested resource not found"
Resource Already Exists Errors
Error: 409-Conflict, Resource already existsCause: Resource exists in OCI but not in state file.
Solution:
# Import existing resource into state
terraform import oci_core_vcn.main ocid1.vcn.oc1.phx.xxxxx
# Then run plan/apply as normal
terraform planPrevention: Always use terraform import for existing infrastructure before managing with Terraform.
State Management Anti-Patterns
Problem: State Drift
Symptoms: Terraform wants to change/destroy resources that were modified outside Terraform (console, API, CLI).
Detection:
terraform plan # Shows unexpected changes
terraform show # Compare state to actual infrastructureSolutions:
Option 1: Refresh state (safe)
terraform refresh # Updates state to match realityOption 2: Import changes (if new resources)
terraform import <resource_type>.<name> <ocid>Option 3: Ignore changes in lifecycle
lifecycle {
ignore_changes = [defined_tags, freeform_tags] # Ignore console tag edits
}Problem: State File Corruption
Symptoms: terraform plan fails with "state file corrupted" or "version mismatch"
Recovery:
# 1. Make backup
cp terraform.tfstate terraform.tfstate.backup
# 2. Try state repair
terraform state pull > recovered.tfstate
mv recovered.tfstate terraform.tfstate
# 3. If that fails, restore from Object Storage versioning
# Or reconstruct with imports (last resort)Prevention: Use Object Storage backend with versioning enabled
Resource Lifecycle Traps
Destroy Failures (Common with Dependencies)
Error: Resource still in useExample: Can't destroy VCN because subnet still exists, can't destroy subnet because instances still attached.
Solution:
# 1. Visualize dependencies
terraform graph | dot -Tpng > graph.png
# 2. Destroy in reverse order
terraform destroy -target=oci_core_instance.web
terraform destroy -target=oci_core_subnet.private
terraform destroy -target=oci_core_vcn.main
# Or use depends_on explicitly:
resource "oci_core_vcn" "main" {
# ...
}
resource "oci_core_subnet" "private" {
vcn_id = oci_core_vcn.main.id
# depends_on is implicit via vcn_id reference
}Timeouts for Long-Running Resources
# Database provisioning takes 15-30 minutes
resource "oci_database_autonomous_database" "prod" {
# ... configuration ...
timeouts {
create = "60m" # Default 20m often not enough
update = "60m"
delete = "30m"
}
}
# Compute instance usually fast, but can timeout on capacity issues
resource "oci_core_instance" "web" {
# ... configuration ...
timeouts {
create = "30m" # Allow retries on "out of capacity"
}
}OCI Landing Zones
What: Pre-built Terraform templates for enterprise OCI architectures
Repository: github.com/oracle-quickstart/oci-landing-zones
Use when:
- Starting new OCI tenancy (greenfield)
- Need CIS OCI Foundations Benchmark compliance
- Want security-hardened baseline
- Multi-environment (dev/test/prod) setup
DON'T use when:
- Brownfield (existing infrastructure) - too opinionated
- Simple single-app deployment - overkill
Key patterns:
- Hub-and-spoke networking
- Centralized logging/monitoring
- Security zones and bastion hosts
- IAM baseline with groups/policies
Cost Optimization for IaC
Use Flex Shapes (50% savings)
# EXPENSIVE - fixed shape
resource "oci_core_instance" "web" {
shape = "VM.Standard2.4" # 4 OCPUs, 60GB RAM, $218/month
}
# CHEAPER - flexible shape
resource "oci_core_instance" "web" {
shape = "VM.Standard.E4.Flex"
shape_config {
ocpus = 4
memory_in_gbs = 60
}
# Cost: (4 × $0.03 + 60 × $0.0015) × 730 = $153/month (30% savings)
}Tag Everything for Cost Tracking
# Define locals for consistent tagging
locals {
common_tags = {
"CostCenter" = "Engineering"
"Environment" = var.environment
"ManagedBy" = "Terraform"
"Project" = var.project_name
}
}
resource "oci_core_instance" "web" {
freeform_tags = merge(
local.common_tags,
{
"Component" = "WebServer"
}
)
}Benefit: Cost reporting by CostCenter, Environment, Project in OCI Console
Progressive Loading References
OCI Terraform Patterns
WHEN TO LOAD `oci-terraform-patterns.md`:
- Setting up provider configuration (multi-region, auth methods)
- Resource Manager stack operations via CLI
- Common resource patterns (VCN, compute, ADB)
- State management with Object Storage backend
- Landing Zone module usage examples
Do NOT load for:
- Quick provider gotchas (NEVER list above)
- Understanding when to use Landing Zone (covered above)
- Lifecycle management patterns (covered above)
---
When to Use This Skill
- Writing Terraform: provider configuration, resource dependencies, lifecycle
- State management: drift, corruption, import/export
- Troubleshooting: authentication failures, "resource already exists", destroy failures
- OCI Landing Zones: when to use, how to customize
- Cost optimization: Flex shapes, tagging strategies
- Production: prevent_destroy, ignore_changes, timeouts
{
"version": "2.0.0",
"organization": "Community",
"author": "Alexander Cedergren",
"date": "January 2026",
"abstract": "Expert knowledge for OCI Infrastructure as Code including Terraform provider patterns, Resource Manager stacks, Landing Zone modules, and multi-region deployment strategies.",
"references": [
"https://registry.terraform.io/providers/oracle/oci/latest/docs",
"https://docs.oracle.com/en-us/iaas/Content/ResourceManager/home.htm",
"https://github.com/oracle-terraform-modules/terraform-oci-landing-zones"
]
}
OCI Terraform and Resource Manager Patterns
Provider Configuration
Basic Provider Setup
terraform {
required_providers {
oci = {
source = "oracle/oci"
version = "~> 5.0"
}
}
}
# API Key authentication
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
private_key_path = var.private_key_path
fingerprint = var.fingerprint
region = var.region
}
# Instance Principal (for OCI compute)
provider "oci" {
auth = "InstancePrincipal"
region = var.region
}
# Resource Principal (for OCI Functions)
provider "oci" {
auth = "ResourcePrincipal"
region = var.region
}Multi-Region Configuration
provider "oci" {
alias = "primary"
region = "us-ashburn-1"
# ... auth config
}
provider "oci" {
alias = "dr"
region = "us-phoenix-1"
# ... auth config
}
# Use provider alias in resources
resource "oci_core_vcn" "primary_vcn" {
provider = oci.primary
compartment_id = var.compartment_id
cidr_block = "10.0.0.0/16"
}
resource "oci_core_vcn" "dr_vcn" {
provider = oci.dr
compartment_id = var.compartment_id
cidr_block = "10.1.0.0/16"
}Resource Manager Stack Operations
CLI Commands
# Create stack from directory
oci resource-manager stack create \
--compartment-id <compartment-ocid> \
--config-source '{"configSourceType":"ZIP_UPLOAD","zipFileBase64Encoded":"<base64-encoded-zip>"}' \
--display-name "my-infrastructure"
# Create stack from Git
oci resource-manager stack create \
--compartment-id <compartment-ocid> \
--config-source '{"configSourceType":"GIT_CONFIG_SOURCE","configurationSourceProviderId":"<provider-ocid>","repositoryUrl":"https://github.com/org/repo","branchName":"main","workingDirectory":"terraform/"}' \
--display-name "my-infrastructure"
# List stacks
oci resource-manager stack list --compartment-id <compartment-ocid>
# Run plan
oci resource-manager job create-plan-job \
--stack-id <stack-ocid>
# Run apply
oci resource-manager job create-apply-job \
--stack-id <stack-ocid> \
--execution-plan-strategy "AUTO_APPROVED"
# Run destroy
oci resource-manager job create-destroy-job \
--stack-id <stack-ocid> \
--execution-plan-strategy "AUTO_APPROVED"
# Get job logs
oci resource-manager job get-job-logs \
--job-id <job-ocid>Common Resource Patterns
VCN with Public and Private Subnets
resource "oci_core_vcn" "main" {
compartment_id = var.compartment_id
cidr_blocks = [var.vcn_cidr]
display_name = "${var.prefix}-vcn"
dns_label = var.dns_label
}
resource "oci_core_internet_gateway" "main" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.main.id
display_name = "${var.prefix}-igw"
}
resource "oci_core_nat_gateway" "main" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.main.id
display_name = "${var.prefix}-natgw"
}
resource "oci_core_service_gateway" "main" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.main.id
display_name = "${var.prefix}-sgw"
services {
service_id = data.oci_core_services.all_services.services[0].id
}
}
# Public subnet (uses IGW)
resource "oci_core_subnet" "public" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.main.id
cidr_block = cidrsubnet(var.vcn_cidr, 8, 0)
display_name = "${var.prefix}-public-subnet"
dns_label = "public"
route_table_id = oci_core_route_table.public.id
security_list_ids = [oci_core_security_list.public.id]
}
# Private subnet (uses NAT)
resource "oci_core_subnet" "private" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.main.id
cidr_block = cidrsubnet(var.vcn_cidr, 8, 1)
display_name = "${var.prefix}-private-subnet"
dns_label = "private"
prohibit_public_ip_on_vnic = true
route_table_id = oci_core_route_table.private.id
security_list_ids = [oci_core_security_list.private.id]
}Compute Instance with Boot Volume Backup
resource "oci_core_instance" "main" {
compartment_id = var.compartment_id
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
shape = "VM.Standard.E4.Flex"
display_name = "${var.prefix}-instance"
shape_config {
ocpus = var.instance_ocpus
memory_in_gbs = var.instance_memory_gbs
}
create_vnic_details {
subnet_id = oci_core_subnet.private.id
assign_public_ip = false
}
source_details {
source_type = "image"
source_id = data.oci_core_images.oracle_linux.images[0].id
}
metadata = {
ssh_authorized_keys = var.ssh_public_key
}
freeform_tags = var.freeform_tags
defined_tags = var.defined_tags
}
# Backup policy
resource "oci_core_volume_backup_policy_assignment" "main" {
asset_id = oci_core_instance.main.boot_volume_id
policy_id = data.oci_core_volume_backup_policies.predefined.volume_backup_policies[0].id # Bronze/Silver/Gold
}Autonomous Database
resource "oci_database_autonomous_database" "main" {
compartment_id = var.compartment_id
db_name = var.adb_name
display_name = "${var.prefix}-adb"
db_workload = "OLTP" # or "DW" for data warehouse
is_auto_scaling_enabled = true
cpu_core_count = var.adb_ocpu_count
data_storage_size_in_tbs = var.adb_storage_tbs
admin_password = var.adb_admin_password
# Network configuration (private endpoint)
subnet_id = oci_core_subnet.private.id
nsg_ids = [oci_core_network_security_group.adb.id]
is_mtls_connection_required = false # Allow TLS-only connections
# Backup configuration
is_auto_backup_enabled = true
# Maintenance window
autonomous_maintenance_schedule_type = "REGULAR"
freeform_tags = var.freeform_tags
defined_tags = var.defined_tags
}State Management
Remote State in Object Storage
terraform {
backend "http" {
address = "https://objectstorage.<region>.oraclecloud.com/p/<par-token>/n/<namespace>/b/<bucket>/o/terraform.tfstate"
update_method = "PUT"
}
}
# Alternative: Use S3-compatible backend
terraform {
backend "s3" {
bucket = "terraform-state"
key = "prod/terraform.tfstate"
region = "us-ashburn-1"
endpoint = "https://<namespace>.compat.objectstorage.<region>.oraclecloud.com"
skip_region_validation = true
skip_credentials_validation = true
skip_metadata_api_check = true
force_path_style = true
}
}Drift Detection
CLI Commands
# Detect drift
oci resource-manager stack detect-stack-drift \
--stack-id <stack-ocid>
# Get drift detection status
oci resource-manager stack get-stack-drift-detection-status \
--stack-id <stack-ocid>
# List resources with drift
oci resource-manager resource-drift-collection list \
--stack-id <stack-ocid>Landing Zone Module Usage
module "landing_zone" {
source = "oracle-terraform-modules/landing-zone/oci"
version = "~> 2.0"
# Required
tenancy_ocid = var.tenancy_ocid
home_region = var.home_region
deploy_regions = ["us-ashburn-1", "us-phoenix-1"]
# Compartments
compartments_configuration = {
enable_delete = false
compartments = {
NETWORK = { name = "Network" }
SECURITY = { name = "Security" }
WORKLOADS = { name = "Workloads" }
}
}
# Network
network_configuration = {
default_compartment_id = module.landing_zone.compartments["NETWORK"].id
vcns = {
HUB-VCN = {
cidr_blocks = ["10.0.0.0/16"]
subnets = {
PUBLIC-SUBNET = { cidr_block = "10.0.0.0/24" }
PRIVATE-SUBNET = { cidr_block = "10.0.1.0/24" }
}
}
}
}
# Security Zones
security_zones_configuration = {
default_compartment_id = module.landing_zone.compartments["WORKLOADS"].id
security_zones = {
PROD-ZONE = {
compartment_id = module.landing_zone.compartments["WORKLOADS"].id
recipe_name = "PROD_RECIPE"
}
}
}
}Best Practices
Module Structure
terraform/
├── modules/
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── network/
│ └── database/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── test/
│ └── prod/
└── shared/
└── data.tfVariable Validation
variable "environment" {
type = string
description = "Environment name (dev, test, prod)"
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "Environment must be one of: dev, test, prod."
}
}
variable "instance_shape" {
type = string
description = "Compute instance shape"
validation {
condition = can(regex("^VM\\.", var.instance_shape))
error_message = "Instance shape must be a VM shape (VM.*)."
}
}Related skills
FAQ
Should I hardcode OCIDs in OCI Terraform?
No. Hardcoded OCIDs break portability across regions and compartments; use variables or data sources instead.
Should I write OCI Terraform from scratch?
Prefer the official oracle-terraform-modules Landing Zone modules, which are CIS-certified and Oracle-maintained, and write custom Terraform only for resources they do not cover.