kreuzwerker / terraform-provider-docker

Terraform Docker provider
Mozilla Public License 2.0
627 stars 189 forks source link

docker_container image forces replacement, when providing manual image (e.g. `nginx:1`) #426

Open nidotls opened 2 years ago

nidotls commented 2 years ago

Community Note

Terraform (and docker Provider) Version

Terraform v1.2.6
on linux_amd64
+ provider registry.terraform.io/kreuzwerker/docker v2.20.0

Affected Resource(s)

Terraform Configuration Files

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "2.20.0"
    }
  }
}

provider "docker" {
  host = "tcp://localhost:2375"
}

# get latest v1 image
data "docker_registry_image" "nginx" {
  name = "nginx:1"
}

# pull latest v1 image
resource "docker_image" "nginx" {
  name          = data.docker_registry_image.nginx.name
  pull_triggers = [data.docker_registry_image.nginx.sha256_digest]
  force_remove  = false
  keep_locally  = true
}

# use latest v1 image
resource "docker_container" "nginx" {
  name  = "nginx"
  image = docker_image.nginx.name
}

Debug Output

https://gist.github.com/thenilsdev/8961e159203c9429601d2eda0aa3f67a

Panic Output

Expected Behaviour

I use watchtower on a staging system. Watchtower checks in regular intervals if all images are up to date. Watchtower does not change nginx:1.2.2 to nginx:1.2.3 but compares the latest nginx:1 sha256 value with the existing one and updates to it. For watchtower to work, the image must be present in the container (e.g. nginx:1). In the examples here only the sha256 value is used, which would work but watchtower would not be able to update, since there is not raw image (e.g. nginx:1).

Output with my example code:

$ docker inspect nginx --format "{{.Image}}" 
sha256:b692a91e4e1582db97076184dae0b2f4a7a86b68c4fe6f91affa50ae06369bf5
$ docker inspect nginx --format "{{.Config.Image}}"
nginx:1

When I change docker_container image to docker_image.nginx.latest it outputs the following:

$ docker inspect nginx --format "{{.Image}}"
sha256:b692a91e4e1582db97076184dae0b2f4a7a86b68c4fe6f91affa50ae06369bf5
$ docker inspect nginx --format "{{.Config.Image}}"
sha256:b692a91e4e1582db97076184dae0b2f4a7a86b68c4fe6f91affa50ae06369bf5

So it would be enough if {{.Config.Image}} could be set from outside, so watchtower can check this value.

Actual Behaviour

Also without changing the registry, docker_container tries to change image from the sha256 to the image name every time

image             = "sha256:b692a91e4e1582db97076184dae0b2f4a7a86b68c4fe6f91affa50ae06369bf5" -> "nginx:1" # forces replacement

Steps to Reproduce

  1. terraform apply
  2. terraform apply
  3. terraform apply

Important Factoids

References

Junkern commented 1 year ago

To be honest, I still don't fully understand your problem regarding watchtower, but I still did some digging and research.

When running docker run --name nginx_test -d nginx:1.23 and docker inspect nginx_test --format '{{.Image}}' we get sha256:23fc29c9268915db1c2d3007ce77cd2d8b499707538b69ea78a2ae860258fac6.

That means, docker automatically converts the nginx:1.23 to an sha and sets the .Image value, we have no influence on that.

So, although the actual behaviour may seem like a bug, it is the correct behaviour. Theoretically, when we compose the diff, we could internally pull the sha value of nginx:1 and compare it to the actual value of the running container.

And the value {{.Config.Image}} is controlled by us, it is currently the same as the value of docker_container.image.

Could you elaborate again what your ideal solution would look like, so you can use watchtower?

Tchoupinax commented 1 year ago

Hello, I just found the ticket and basically I have the same issue. I deployed a container with a static image name, and when I make another plan, terraform compare the sha256 of the image and the name, and consider that the image has changed. So each time, the container is destroyed then deployed. How can we fix that?

Tchoupinax commented 1 year ago

@Junkern, reading your paragraph, I understood that Docker transform the name of the image to the hash string. So, I tried to replace the image by the sha256 string.

~ hostname  = "5b2166d4c897" -> (known after apply)
~ id        = "5b2166d4c897d9dcdbcbbb9547acf0b3f163e0370b7c2d9cb1eccf3276a039fb" -> (known after apply)
~ image     = "sha256:d125b701503f942a08f443c7bff54a0600ec327da3ce2fd46086db3d4b5d3ca3" -> "woodpeckerci/woodpecker-server:next-alpine" # forces replacement
~ init      = false -> (known after apply)
~ ipc_mode  = "private" -> (known after apply)

But the issue is, when I made the change, I discovered that terraform compared the sha256 with the name of the image... So I think that we could find the current name of the image.

  # docker_image.woodpecker_server_image must be replaced
-/+ resource "docker_image" "woodpecker_server_image" {
      ~ id          = "sha256:d125b701503f942a08f443c7bff54a0600ec327da3ce2fd46086db3d4b5d3ca3woodpeckerci/woodpecker-server:next-alpine" -> (known after apply)
      ~ image_id    = "sha256:d125b701503f942a08f443c7bff54a0600ec327da3ce2fd46086db3d4b5d3ca3" -> (known after apply)
      ~ name        = "woodpeckerci/woodpecker-server:next-alpine" -> "sha256:d125b701503f942a08f443c7bff54a0600ec327da3ce2fd46086db3d4b5d3ca3" # forces replacement
      ~ repo_digest = "woodpeckerci/woodpecker-server@sha256:caac5d3e341ebe2bc9e0883c072d27fc3656dd91fbc49c8d8eb304e9593996dd" -> (known after apply)
    }
Tchoupinax commented 1 year ago

Hello,

Finally a friend of mine gave me the tip: we should not use the name attribute but the image_id, then it works. For the original example, it would do:

# use latest v1 image
resource "docker_container" "nginx" {
  name  = "nginx"
  image = docker_image.nginx.image_id
}

Note that even if it works well, when you list running containers on the machine to this the image, you only see hash and not more the name of the image. I don't know exactly if it's a bug or not (that it does not work with .name) but with image_id the expected behaviour with Terraform is reached!

vnghia commented 1 year ago

A workaround I found is using label.

data "docker_image" "ubuntu" {
  name = "ubuntu:22.04"
}

resource "docker_container" "ubuntu" {
  name  = "foo"
  image = data.docker_image.ubuntu.name
  labels = [
     {
        label = "image.id"
        value = data.docker_image.ubuntu.id
     }
  ]
  lifecycle {
    ignore_changes = [name]
  }
}

This way, we can keep the image name as ubuntu:22.04 and the resource will automatically changes if the image name changes in our code (since it will also change image.id and trigger a replacement)