OCI Custom Image lookup in Terraform
Mar 14, 2025One common use case I have when working with Terraform and OCI is using Custom Images when deploying Compute Instances in my training and lab environments. While I do a fair amount of configuration at deployment time, there are still some things I often want customized in my base images. I will start with an OCI Platform image like Oracle Linux 9, deploy to an Instance, customize to my liking, and then finally create a Custom Image to use as a base image for future deployments.
This works well, as I can reference this Custom Image by its OCID. The problem is there is a lifecycle management aspect to these Custom Images. My image customizations may change overtime, some package requirements might change, I may want to upgrade or patch the image, etc. Every time there is a new “base image”, I will need to update the OCID to use. Referencing this OCID as a variable makes this pretty easy, but it would be nice if I could just always look up the most recent version of my “base image”. What I really want is to use a Terraform Data Source to dynamically determine the appropriate Custom Image to use.
AWS Provider Example
If I were to bring this use case over to the AWS Terraform provider, the aws_ami
Data Source handles this very well. There are two arguments that help make this happen.
The first is the ability to filter
, which gives us options with tags. When we are registering AMI, we can tag them with appropriate information to help us dynamically find the right image. Maybe we add an Application
tag so we can identify images created for specific apps. We also might want to add a Tier
tag, so we can call out development versus training images.
The second argument is most_recent
, which will return only the most recently created image when set to true
.
Here is an example of what this data source would look like in Terraform. You can see the data being used in the ami_id
output value.
data "aws_ami" "selected_ami" {
owners = ["self"]
most_recent = true
filter {
name = "tag:Application"
values = ["PeopleSoft"]
}
filter {
name = "tag:Tier"
values = ["Training"]
}
}
output "ami_id" {
value = data.aws_ami.selected_ami.id
}
OCI Provider
Unfortunately, this same approach of filtering on tags will not work with the OCI Terraform Provider. While there is a oci_core_images
Data Source we can use, it does not support filtering on tags and does not have a “most recent” option. But I have come up with a work around!
We can’t filter on tags but we can filter on display_name
. What makes this powerful is Custom Images can have the same name, they don’t need to be unique. The hack I use to make this work is to essentially move my tag values into the display name, making it smart coded. If we pick a standard naming convention that includes values like application and tier, and we always name it the same as new versions of the Custom Image are created, we can use this filter to return a list of relevant images. For example, if our convention is [APP]_[TIER]_IMAGE
then we might name a PeopleSoft Training image PSFT_TRAIN_IMAGE
.
This works in getting a list of images, but how do we select the most recent? This is where the sort_by
and sort_order
arguments come into play. If we set sort_by="TIMECREATED"
and sort_order="DESC"
, then the first image in our list will always be the most recent. We can reference the image we want in our Data Source list with [0]
.
Lastly we also want to filter on state="AVAILABLE"
, as we don’t want to return images that are not in a useable state.
Here is an example of what usage of this data source would look like in Terraform.
# Variables
variable "compartment_id" {
description = "Compartment OCID for Custom Images"
}
variable "custom_image_name" {
description = "Custom Image Name - '[APP]_[TIER]_IMAGE'"
}
# Main
data "oci_core_images" "instance_image" {
# Filter by display name and sort by most recent
compartment_id = var.compartment_id
display_name = var.custom_image_name
state = "AVAILABLE"
sort_by = "TIMECREATED"
sort_order = "DESC"
}
locals {
# Get first image in list, which is most recent
most_recent_image = data.oci_core_images.instance_image.images[0]
}
# Outputs
output "most_recent_image_id" {
value = local.most_recent_image.id
}
output "most_recent_image_time_created" {
value = local.most_recent_image.time_created
}
After deploying the Terraform code above, we can run a plan
and see our Custom Image details in the output
. I’m using some environment variables to organize a few things and then passing in as Terraform variable arguments. You can see I’m setting a local
to the most recent Custom Image and then using attributes from that in a few output
values. This same approach will work well in any Compute Instance resource blocks you create using the most recent Custom Image dynamically.
COMP_ID="ocid1.compartment.oc1..aaaaaaaaisvbrpl..."
APP_NAME="PSFT"
TIER_NAME="TRAIN"
IMAGE_NAME="${APP_NAME}_${TIER_NAME}_IMAGE"
terraform plan \
-var compartment_id=$COMP_ID \
-var custom_image_name=$IMAGE_NAME
data.oci_core_images.instance_image: Reading...
data.oci_core_images.instance_image: Read complete after 0s [id=CoreImagesDataSource-636517373]
Changes to Outputs:
+ most_recent_image_id = "ocid1.image.oc1.iad.aaaaaaaay..."
+ most_recent_image_time_created = "2025-03-05 01:18:50.511 +0000 UTC"
It would be nice if the provider supported filtering by tags, but I feel this is an OK workaround. There are a few other arguments that are also very handy and work great for Platform Images. You can use operating_system
to select things like Oracle Linux, and then use operating_system_version
to select 8.9
. If you want to further restrict on images that support certain shapes, you can do that as well.
I will close with one word of caution when doing dynamic lookup of Custom Image OCIDs. You do have to be careful with this approach when doing multiple terraform apply
runs. If a new custom image is created, this will change the OCID used, which changes the plan and might trigger your instance to be rebuilt. I found this approach most helpful when building custom images in a lab setting. A high volume of changes to the current base image, deployed to Compute Instances that are very short lived. However, you can move this to more permanent workloads if you are careful with your Terraform code. You can ignore changes to the Custom Image OCID after initial deployment with the ignore_changes
attribute for source_details
.
Here is an example of what that might look like.
resource "oci_core_instance" "example" {
source_details {
source_id = local.most_recent_image.id # Recent Custom Image OCID
source_type = "image"
}
lifecycle {
ignore_changes = [source_details] # Ignore Custom Image changes
}
}