Home Creating Docker images with Packer with HCL
Post
Cancel

Creating Docker images with Packer with HCL

Since Packer 1.5 HCL2 is supported, and even though its still in beta, and some features are still missing, for those used to HCL writing hashicorp modules, it beats json previous templating. In this post we will use Packer with HCL to create two docker images, one alpine based and one debian based, that will run nginx.

Since I’m new to Packer, I’ve decided to try it out, as its a very useful tool when building images with a lot of provisioners. Reminds me a lot of Vagrant usage, and its fairly simple to build a Docker image, without building from a Dockerfile.

Requirements

  • Packer installed with version 1.5+
  • Docker installed (I’m running wsl2 with ubuntu in windows for this post)

Packer Terminology

So for those used to vagrant, there are plenty of similarities.

Builders - Would be the same as your vagrant providers, where you define your boxes etc. Provisioners - Same as in vagrant (i.e ansible) Communicators - How do you the infra where your image is being built (i.e. ssh, docker daemon, etc..) Post-Processors - These are tasks that run post build

Translating json to HCL

Since HCL is still very fresh within Packer, its still not possible to use an automated tool to convert json files (where you have plenty of samples to learn from) to .pkr.hcl files.

So, lets check a json example of how to build a docker image within packer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "builders": [
        "type":"docker",
        "image": "alpine:3.9",
        "commit": true,
        "name": "alpine",
        "changes": [
            "CMD [\"nginx\", \"-g\", \"daemon off;\"]"
        ]
    ],
    "provisioners":[
        "type": "shell",
        "inline": [
            "apk add nginx"
        ]
    ],
    "post-processors":[
        "type": "docker-tag",
        "repository":"ilhicas/nginx-debian-packer-build",
        "tag":["1.0"]
    ]
}

So for those that are used to Dockerfiles the “builders” are counterintuitive, as one would believe this would be where they would “change” the base image. However this process is done at the provisioners step, and for those used to Vagrant, that makes absolute sense.

So, since HCL the one-to-one translation wasn’t honoured, and documentation is still lacking. However, there are a few hints in the documentation that you are able to split this declaration into multiple files.

So for the “builders” key, this would translate now into “source” this is inline with vagrant as well.

So this would become

1
2
3
4
5
6
7
source "docker" "alpine" {
  image   = "alpine:3.9"
  commit  = true
  changes = [
      "CMD [\"nginx\", \"-g\", \"daemon off;\"]"
  ]
}

So, lets add another source in here and create our files with .pkr.hcl file extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Our file is named sources.pkr.hcl
source "docker" "alpine" {
  image   = "alpine:3.9"
  commit  = true
  changes = [
      "CMD [\"nginx\", \"-g\", \"daemon off;\"]"
  ]
}

source "docker" "debian" {
  image   = "debian:10"
  commit  = true
  changes = [
      "CMD [\"nginx\", \"-g\", \"daemon off;\"]"
  ]
}

But we now have two images, as we might want to create multiple images, for multiple arch’s etc..

So let’s define two different files (for now and I will expand on why) on how we will provision these images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# debian.pkr.hcl
build {
  sources = [
    "source.docker.debian",
  ]

  provisioner "shell" {
    inline = [
        "apt-get update",
        "apt-get install -y nginx"
      ]
    // only = ["debian"] # This will only be available with this issue https://github.com/hashicorp/packer/issues/9094
    // Expected version 1.5.6
  }

  post-processor "docker-tag" {
    repository = "ilhicas/nginx-debian-packer-build"
    tag        = ["1.0"]
  }

}

And for our alpine based image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# alpine.pkr.hcl
build {
  sources = [
    "source.docker.alpine",
  ]

  provisioner "shell" {
    inline = [
        "apk add nginx"
https://github.com/Ilhicas/packer-hcl/tree/master/docker    // only = ["alpine"] # This will only be available with this issue https://github.com/hashicorp/packer/issues/9094
    // Expected version 1.5.6
  }

  post-processor "docker-tag" {
    repository = "ilhicas/nginx-alpine-packer-build"
    tag        = ["1.0"]
  }
}

So, now when we run the packer build command in our root directory, it will grab all of our pkr.hcl files and build all our declarations in parallel.

1
packer build .

However, as you might see I’ve commented out the only attribute as it still not usable in version 1.5, but expected to be on 1.6

This would allow us to make use of the command

1
packer build -only=alpine

And that would allow us to merge both files in a single one with all the provisioner blocks in a single build block.

As it stands its still not able to also target a single template as it will fail on the sources.

As always you may find these examples, and future ones for hcl in the following repo:

https://github.com/Ilhicas/packer-hcl/tree/master/docker

And that’s a wrap, this is a short Post based on my learnings while using Packer for the first time.

Feel free to follow me on https://twitter.com/Ilhicas if you like my posts.

This post is licensed under CC BY 4.0 by the author.