
Infrastructure As Code
- 11 installs
- 22 repo stars
- Updated May 28, 2026
- acedergren/agentic-tools
infrastructure-as-code is a Claude Code skill for writing Terraform for OCI and troubleshooting provider errors, state, and drift.
About
infrastructure-as-code is a Claude Code skill for writing Terraform for OCI and troubleshooting provider errors. A developer uses it when managing state files, implementing Resource Manager stacks, or recovering from drift and 409 conflicts. It covers terraform-provider-oci gotchas, resource-lifecycle anti-patterns, state recovery, and authentication precedence.
- Terraform for OCI: provider gotchas, state, Resource Manager
- State drift, 409-conflict, and corruption recovery recipes
- Lifecycle, for_each, and authentication-precedence anti-patterns
Infrastructure As Code by the numbers
- 11 all-time installs (skills.sh)
- Ranked #841 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
infrastructure-as-code capabilities & compatibility
Free; guidance plus Terraform/OCI CLI against an existing tenancy.
- Capabilities
- infrastructure as code · terraform · state recovery
- Works with
- terraform · oracle
- Use cases
- devops · ci cd
- Pricing
- Free
What infrastructure-as-code says it does
NEVER hardcode OCIDs in Terraform (breaks portability)
NEVER store Terraform state locally for team use
npx skills add https://github.com/acedergren/agentic-tools --skill infrastructure-as-codeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 22 |
| Last updated | May 28, 2026 |
| Repository | acedergren/agentic-tools ↗ |
What it does
Write Terraform for OCI and recover from provider errors, state drift, and 409 conflicts.
Who is it for?
OCI Terraform authoring and recovering from state drift, 409 conflicts, and corruption.
Skip if: Non-OCI Terraform or general programming unrelated to infrastructure.
When should I use this skill?
Use when writing Terraform for OCI, troubleshooting provider errors, managing state files, or implementing Resource Manager stacks.
By the numbers
- 4-level authentication precedence order
- state-file corruption recovery in 4 steps
Files
OCI Infrastructure as Code - Expert Knowledge
NEVER Do This
NEVER hardcode OCIDs in Terraform (breaks portability)
# WRONG - breaks when moving between regions/tenancies
resource "oci_core_instance" "web" {
compartment_id = "ocid1.compartment.oc1..aaaaaa..." # Hardcoded!
subnet_id = "ocid1.subnet.oc1.phx.bbbbbb..." # Hardcoded!
}
# RIGHT - variables or data sources
resource "oci_core_instance" "web" {
compartment_id = var.compartment_ocid
subnet_id = data.oci_core_subnet.existing.id
}NEVER hardcode availability domain names
# WRONG - AD names are tenant-specific (fMgC: prefix differs per tenancy)
availability_domain = "fMgC:US-ASHBURN-AD-1"
# RIGHT - query 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 use `preserve_boot_volume = true` in dev/test (default behavior is true!)
# WRONG - default! Orphans boot volumes when instance is destroyed
resource "oci_core_instance" "dev" {
# preserve_boot_volume not set = defaults to true
}
# RIGHT - explicit cleanup in dev/test
resource "oci_core_instance" "dev" {
preserve_boot_volume = false
}Cost impact: dev team with 10 instances cycling through testing = $50–500/month in silent orphaned volumes.
NEVER skip `lifecycle` blocks on production databases
# RIGHT - protect production resources from accidental destroy
resource "oci_database_autonomous_database" "prod" {
lifecycle {
prevent_destroy = true
ignore_changes = [defined_tags] # Ignore tag edits made via console
}
}Without this: a mistyped terraform destroy -target deletes a production database permanently.
NEVER use `count` for resources that shouldn't be replaced on list reorder
# WRONG - reordering instance_names from ["web1","web2"] to ["web0","web1","web2"]
# causes Terraform to RECREATE all instances
resource "oci_core_instance" "web" {
count = length(var.instance_names)
display_name = var.instance_names[count.index]
}
# RIGHT - for_each with stable keys
resource "oci_core_instance" "web" {
for_each = toset(var.instance_names)
display_name = each.value
}NEVER store Terraform state locally for team use
# WRONG - no locking, no collaboration
terraform { backend "local" {} }
# RIGHT - OCI Object Storage with S3-compatible backend
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
}
}OCI Provider Authentication Gotchas
Authentication precedence (silent override is a common footgun): 1. Explicit provider block credentials 2. TF_VAR_* environment variables 3. ~/.oci/config file (DEFAULT profile) 4. Instance Principal (auth = "InstancePrincipal")
Common mistake: setting env vars but an explicit provider block overrides them without any warning.
Instance Principal for Terraform running on OCI compute:
provider "oci" {
auth = "InstancePrincipal"
region = var.region
}Critical: the instance must be in the dynamic group BEFORE Terraform runs. If added after, auth fails with the cryptic error: "authorization failed or requested resource not found".
State Management
Fixing State Drift
State drift happens when resources are modified outside Terraform (console, CLI, API).
terraform plan # Shows unexpected changes — identifies drift
terraform refresh # Updates state to match actual OCI reality (safe read-only op)
# For new resources created outside Terraform:
terraform import oci_core_vcn.main ocid1.vcn.oc1.phx.xxxxxSuppress drift from console tag edits (common source of noise):
lifecycle {
ignore_changes = [defined_tags, freeform_tags]
}Fixing "409 Conflict — Resource Already Exists"
Cause: resource exists in OCI but not in state file (e.g., created manually or previous import failure).
terraform import oci_core_vcn.main ocid1.vcn.oc1.phx.xxxxx
terraform plan # Should now show no changes for that resourceState File Corruption Recovery
# 1. Backup first
cp terraform.tfstate terraform.tfstate.backup
# 2. Try state pull repair
terraform state pull > recovered.tfstate
mv recovered.tfstate terraform.tfstate
# 3. If that fails, restore from Object Storage versioning
# 4. Last resort: reconstruct with terraform import for each resourcePrevention: enable Object Storage bucket versioning on the state backend.
Destroy Failures (Dependency Order)
Error: Resource still in useOCI enforces strict dependency order on destroy: instances must be terminated before subnets, subnets before VCN, etc.
# Visualize the dependency graph
terraform graph | dot -Tpng > graph.png
# Destroy in reverse dependency order
terraform destroy -target=oci_core_instance.web
terraform destroy -target=oci_core_subnet.private
terraform destroy -target=oci_core_vcn.mainTimeouts for Long-Running OCI Resources
OCI resource provisioning times vary significantly. Default Terraform timeouts often cause false failures:
# Autonomous Database: 15-30 min to provision (default 20m is borderline)
resource "oci_database_autonomous_database" "prod" {
timeouts {
create = "60m"
update = "60m"
delete = "30m"
}
}
# Compute: usually fast, but capacity issues can cause retries
resource "oci_core_instance" "web" {
timeouts {
create = "30m"
}
}OCI Landing Zones
Use oracle-terraform-modules/terraform-oci-landing-zones for:
- Greenfield tenancy setup requiring CIS OCI Foundations Benchmark compliance
- Multi-environment (dev/test/prod) with hub-and-spoke networking
- Centralized logging, Cloud Guard, and Security Zones
Do NOT use Landing Zone for:
- Brownfield (existing infrastructure) — too opinionated, causes state conflicts
- Simple single-app deployments — the module overhead exceeds the value
Progressive Loading Reference
Load `references/oci-terraform-patterns.md` when:
- Setting up provider configuration (multi-region, auth methods)
- Resource Manager stack operations via CLI
- Common resource patterns with full HCL examples (VCN, compute, ADB)
- Landing Zone module usage examples
Do NOT load for NEVER-list gotchas, lifecycle management, or state troubleshooting — this file covers those.
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.*)."
}
}