OCI Container Instances: Serverless Containers Without Kubernetes

OCI Container Instances sit between OCI Functions and OKE. Functions are best for short-lived event handlers. OKE handles long-running, scalable production services that need full Kubernetes orchestration. Container Instances fills the middle: long-running containers without the overhead of provisioning a Kubernetes cluster, node pools, ingress controllers, or any of the surrounding infrastructure. You define the container spec, choose a shape, and the instance runs until you stop it.

Step 1: IAM Policy

resource "oci_identity_policy" "container_instances_policy" {
  compartment_id = var.compartment_id
  name           = "container-instances-policy"
  statements = [
    "Allow group DevOpsEngineers to manage compute-containers in compartment id COMPARTMENT_OCID",
    "Allow group DevOpsEngineers to read repos in compartment id COMPARTMENT_OCID",
    "Allow dynamic-group ContainerInstances to read secret-bundle in compartment id COMPARTMENT_OCID"
  ]
}

Step 2: Provision a Container Instance

resource "oci_container_instances_container_instance" "orders_worker" {
  compartment_id      = var.compartment_id
  display_name        = "orders-background-worker"
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name

  shape = "CI.Standard.E4.Flex"
  shape_config {
    ocpus         = 2
    memory_in_gbs = 8
  }

  vnics {
    subnet_id             = var.private_subnet_id
    nsg_ids               = [var.worker_nsg_id]
    is_public_ip_assigned = false
    display_name          = "orders-worker-vnic"
  }

  image_pull_secrets {
    secret_type       = "VAULT"
    secret_id         = var.ocir_auth_token_secret_id
    registry_endpoint = "me-jeddah-1.ocir.io"
  }

  containers {
    image_url    = "me-jeddah-1.ocir.io/NAMESPACE/production/orders-worker:1.4.2"
    display_name = "orders-worker"

    environment_variables = {
      QUEUE_URL     = "https://cell1.queue.messaging.me-jeddah-1.oci.oraclecloud.com/20190401/queues/QUEUE_OCID"
      LOG_LEVEL     = "INFO"
      REGION        = "me-jeddah-1"
      MAX_BATCH     = "10"
    }

    resource_config {
      vcpus_limit    = 2
      memory_limit_in_gbs = 8
    }

    health_checks {
      health_check_type = "HTTP"
      port              = 8080
      path              = "/health"
      interval_in_seconds = 30
      timeout_in_seconds  = 5
      failure_threshold   = 3
      success_threshold   = 1
    }
  }

  # Inject secrets from OCI Vault as volume mounts
  volumes {
    volume_type = "SECRET"
    name        = "db-credentials"
    configs {
      secret_id  = var.db_password_secret_id
      file_name  = "db_password"
    }
  }

  container_restart_policy = "ON_FAILURE"

  defined_tags = {
    "Operations.Environment" = "production"
    "Operations.ManagedBy"   = "terraform"
  }
}

output "container_instance_id" {
  value = oci_container_instances_container_instance.orders_worker.id
}

Step 3: Multi-Container Sidecar Pattern

resource "oci_container_instances_container_instance" "orders_api_with_sidecar" {
  compartment_id      = var.compartment_id
  display_name        = "orders-api-with-log-sidecar"
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name

  shape = "CI.Standard.E4.Flex"
  shape_config { ocpus = 4; memory_in_gbs = 16 }

  vnics {
    subnet_id             = var.private_subnet_id
    is_public_ip_assigned = false
  }

  image_pull_secrets {
    secret_type       = "VAULT"
    secret_id         = var.ocir_auth_token_secret_id
    registry_endpoint = "me-jeddah-1.ocir.io"
  }

  # Main application container
  containers {
    image_url    = "me-jeddah-1.ocir.io/NAMESPACE/production/orders-api:2.1.0"
    display_name = "orders-api"
    environment_variables = { DB_HOST = var.db_host, LOG_DIR = "/var/log/app" }
    resource_config { vcpus_limit = 3; memory_limit_in_gbs = 12 }
  }

  # Log shipping sidecar
  containers {
    image_url    = "me-jeddah-1.ocir.io/NAMESPACE/tools/log-shipper:1.0.0"
    display_name = "log-shipper"
    environment_variables = { LOGGING_ENDPOINT = var.oci_logging_endpoint, LOG_DIR = "/var/log/app" }
    resource_config { vcpus_limit = 0.5; memory_limit_in_gbs = 1 }
  }

  # Shared volume for log files between containers
  volumes {
    volume_type = "EMPTYDIR"
    name        = "app-logs"
  }

  container_restart_policy = "ALWAYS"
}

Operational Notes

Container Instances do not auto-scale. If you need scale-out, use OKE. Container Instances are appropriate for: background workers that process from a queue at a fixed concurrency, scheduled jobs that run on a timer and exit, internal tooling that needs to run continuously but does not need multiple replicas, and migration workloads that need a long-running process without building a Kubernetes deployment for a one-time job.

Vault secret injection via volumes means the secret value is written to a file inside the container at the specified path. Your application reads it from the filesystem rather than from an environment variable. This is more secure than environment variables because environment variable values are visible in process listings and debug output, while file contents require filesystem access to read.

Regards,
Osama

#OCI #OracleCloud #ContainerInstances #Serverless #Docker #Terraform #IaC #TechBlog #Oracle #PlatformEngineering #CloudNative #OracleCloudInfrastructure #Kubernetes #OKE #Sidecar #VaultIntegration #OCIR #ContainerRegistry #DevOps #CloudContainers

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.