Using count with aws_instance and static IP

Is there any way to do this? I came across this issue, Configuring Private IP Addresses on aws instance · Issue #861 · hashicorp/terraform · GitHub
but I can’t use numbers in an object apparently:
│ 123: type = object({
│ 124: “0” = string
│ 125: “1” = string
│ 126: })

│ Object constructor map keys must be attribute names.

Is there a better way to do static IPs with a count.index?

Hi @solarflow99,

The issue you linked to is from eight years ago, from the very early days of Terraform. I would not suggest paying much attention to anything you see in that issue.

With that said, I’m a little unsure what exactly you are aiming to achieve from the information you shared. There’s not really anything special about the private_ip argument of aws_instance from Terraform’s own perspective, so the same techniques you might use for populating any argument based on count.index could apply here. The main thing is deciding what the rule is for assigning a unique IP address to each of your instances.


If you want to assign consecutive IP addresses starting from a particular address then you could potentially do that with something like the following:

variable "instances" {
  type = object({
    count          = number
    cidr_prefix    = string
    first_host_num = optional(number, 1)
  })
  nullable = false
}

resource "aws_instance" "example" {
  count = var.instances.count

  # ...
  private_ip = cidrhost(
    var.instances.cidr_prefix,
    var.instances.first_host_num + count.index,
  )
}

The main interesting thing going on here is using the cidrhost function to calculate an IP address to use. This requires both the CIDR block of the network to create the addresses in and the number to assign to the first instance. The others will then be assigned consecutive addresses after that, as long as there are enough unique addresses in the given subnet.

For example, if you set the input variable like this…

instances = {
  count          = 3
  cidr_prefix    = "192.168.1.0/24"
  first_host_num = 5
}

…then that should generate the following IP addresses:

  • aws_instance.example[0] has 192.168.1.5
  • aws_instance.example[1] has 192.168.1.6
  • aws_instance.example[2] has 192.168.1.7

Another possibility would be to have the user of your module specify exactly which IP addresses to use, but if that were the goal then I think it would make more sense to use for_each using the IP addresses as the tracking keys, since count.index wouldn’t mean anything for these.

variable "instances" {
  type     = object({
    ip_addrs = set(string)
  })
  nullable = false
}

resource "aws_instance" "example" {
  for_each = var.instances.ip_addrs

  # ...
  private_ip = each.value
}

In this case the user of the module might set the variable like this…

instances = {
  ip_addrs = [
    "192.168.3.4",
    "192.168.3.56",
    "192.168.3.129",
  ]
}

…which would declare instances like this:

  • aws_instance.example["192.168.3.4"] has 192.168.3.4
  • aws_instance.example["192.168.3.56"] has 192.168.3.56
  • aws_instance.example["192.168.3.129"] has 192.168.3.129

Adding and removing elements from that ip_addrs set would then represent adding and removing EC2 instances. The number of elements of the set is the desired number of instances, and the values specify the fixed IP addresses to use.

hi, thanks very much for the reply. Here is a better example of what i’m trying to do:

In project_vars.tf:

variable "ec2_ip" {
  type        = map(any)
  default = {
    "docker1"   = "10.99.50.110"
    "docker2"    = "10.99.50.121"
    "docker3"    = "10.99.50.132"
  }
}

In main.tf

module "ec2" {
    ec2_ip = var.ec2_ip
    ...
}

In module ec2. variables.tf:

variable "docker1_esup" {
  type = object({
    docker1            = string
   docker2            = string
   docker3            = string
  })
}

In modules ec2. main.tf:

resource "aws_instance" "docker1" {
    private_ip             = "var.ec2_ip[count.index]
   ...
}
resource "aws_eip" "docker1" {
  vpc                        = true
  count                    = var.docker1["instance_count"]
  instance               = aws_instance.docker1[count.index].id
}

This still wouldn’t spread them across zones, but it would be nice if it had worked. I suppose looking your second scenario might work but the “aws_eip” block would also need a way of knowing the index.

So how can you declare this as a variable and pass it to the module? Because it doesn’t work they way you showed, that looked like a nested variable, i’ve never seen that before:

variable "instances" {
  ip_addrs = [
    "10.99.50.110",
    "10.99.50.121",
    "10.99.50.132",
  ]
}

Error: Unsupported argument

│ ip_addrs = [

│ An argument named “ip_addrs” is not expected here.

One more problem with this is how to get the each of the names unique. With count its easy to append the count.index to the tag name. Any idea how that can be done with your example?

Anyone know how this can be done?