• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Packer
  • Install
  • Tutorials
  • Documentation
  • Guides
  • Plugins
  • Try Cloud(opens in new tab)
  • Sign up
Configuration Language

Skip to main content
1 tutorial
  • Upgrade Packer JSON Template to HCL2

  • Resources

  • Tutorial Library
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  1. Developer
  2. Packer
  3. Tutorials
  4. Configuration Language
  5. Upgrade Packer JSON Template to HCL2

Upgrade Packer JSON Template to HCL2

  • 9min

  • PackerPacker

As of version 1.7.0, HCL2 is the preferred way to write Packer templates. You can use the hcl2_upgrade command to transition your existing Packer JSON template to HCL2. This enables you to preserve your existing workflows while leveraging HCL2’s advanced features like variable interpolation and configuration composability.

In this tutorial, you will upgrade a Packer JSON template that builds a Docker image to HCL2. Then, you will add provisioners and post processors to the upgraded HCL2 template.

Prerequisites

This tutorial assumes that you are familiar with the standard Packer workflow. If you are new to Packer, complete the Get Started tutorials first. These tutorials will also introduce you to HCL2 blocks.

For this tutorial, you will need:

  • the Packer CLI 1.7+ installed locally
  • Docker

Clone the sample repository

Clone the sample repository for this tutorial, which contains a Packer template to create a Docker image.

$ git clone https://github.com/hashicorp/learn-packer-upgrade-json-config.git

Change into the repository directory.

$ cd learn-packer-upgrade-json-config

Review Packer template

Open docker-ubuntu.json.

{
  "variables": {
    "image": "ubuntu:xenial"
  },
  "builders": [
    {
      "type": "docker",
      "image": "{{user `image`}}",
      "commit": true
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "echo Hello world!"
      ]
    }
  ]
}

This is a standard Packer JSON template that has a variable, a builder and a provisioner. This template runs an ubuntu:xenial Docker container, prints the "Hello world!" string, then saves the container to a Docker image.

Upgrade Packer JSON template to HCL2

Use the hcl2_upgrade command to upgrade the JSON template to HCL2. The -with-annotations flag provides context for the auto-generated template.

$ packer hcl2_upgrade -with-annotations docker-ubuntu.json
Successfully created docker-ubuntu.json.pkr.hcl

Review the HCL2 configuration

Open the newly created Packer template, docker-ubuntu.json.pkr.hcl.

# This file was autogenerated by the 'packer hcl2_upgrade' command. We
# recommend double checking that everything is correct before going forward. We
# also recommend treating this file as disposable. The HCL2 blocks in this
# file can be moved to other files. For example, the variable blocks could be
# moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
# suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
# once they also need to be in the same folder. 'packer inspect folder/'
# will describe to you what is in that folder.

# Avoid mixing go templating calls ( for example ```{{ upper(`string`) }}``` )
# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be
# executed together and the outcome will be unknown.

# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
# constraints documentation
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.
variable "image" {
  type    = string
  default = "ubuntu:xenial"
}

# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "docker" "autogenerated_1" {
  commit = true
  image  = "${var.image}"
}

# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
  sources = ["source.docker.autogenerated_1"]

  provisioner "shell" {
    inline = ["echo Hello world!"]
  }
}

The generated HCL2 template file contains three blocks:

  1. A variable block that defines the image variable,
  2. A source block that defines the builder Packer will use to build the image, and
  3. A build block, a composite block, that defines what Packer will execute when running packer build docker-ubuntu.json.pkr.hcl.

The build block is similar to the full JSON build template file, with the "variables" and "source" block defining values and builders Packer will use for your images. You can reference "variables" and "source" blocks across multiple HCL2 template files or builds — see build-level source blocks for an example on source block reuse.

Update generated template

The hcl2_upgrade command automatically maps and generates the relevant HCL2 block.

You should rename these blocks to accurately represent and describe the resource. Rename docker.autogenerated_1 to docker.ubuntu.

- source "docker" "autogenerated_1" {
+ source "docker" "ubuntu" {
    commit = true
    image  = "${var.image}"
  }

  build {
-   sources = ["source.docker.autogenerated_1"]
+   sources = ["source.docker.ubuntu"]
   ## …
  }

In addition, since this Packer template uses the Docker v0.0.7 plugin, add the following packer block to the top of docker-ubuntu.json.pkr.hcl. This ensures Packer will retrieve the Docker plugin that fulfills the version contraint, so you can consistently generate an image from this template.

packer {
  required_plugins {
    docker = {
      version = ">= 0.0.7"
      source = "github.com/hashicorp/docker"
    }
  }
}

Build Packer image

First, initialize your template.

$ packer init .

Then, build your image. The packer build . command loads all the contents in the current directory. You can also build specific images by specifying the template file directly.

$ packer build .
docker.ubuntu: output will be in this color.

==> docker.ubuntu: Creating a temporary directory for sharing data...
==> docker.ubuntu: Pulling Docker image: ubuntu:xenial
    docker.ubuntu: xenial: Pulling from library/ubuntu
    docker.ubuntu: Digest: sha256:eed7e1076bbc1f342c4474c718e5438af4784f59a4e88ad687dbb98483b59ee4
    docker.ubuntu: Status: Image is up to date for ubuntu:xenial
    docker.ubuntu: docker.io/library/ubuntu:xenial
==> docker.ubuntu: Starting docker container...
    docker.ubuntu: Run command: docker run -v /Users/youruser/.packer.d/tmp435754690:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu:xenial
    docker.ubuntu: Container ID: bc3bcc9ed53bc2291fe2c31a93521ccd3ff45da38e2781ff9a57fe313f297dfc
==> docker.ubuntu: Using docker communicator to connect: 172.17.0.3
==> docker.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell800525815
    docker.ubuntu: Hello world!
==> docker.ubuntu: Committing the container
    docker.ubuntu: Image ID: sha256:e763bc0ceb4873075a28b80986aa344d489861f4237a2049bd95025c1ec21882
==> docker.ubuntu: Killing the container: bc3bcc9ed53bc2291fe2c31a93521ccd3ff45da38e2781ff9a57fe313f297dfc
Build 'docker.ubuntu' finished after 4 seconds 487 milliseconds.

==> Wait completed after 4 seconds 487 milliseconds

==> Builds finished. The artifacts of successful builds are:
--> docker.ubuntu: Imported Docker image: sha256:e763bc0ceb4873075a28b80986aa344d489861f4237a2049bd95025c1ec21882

List the images to verify Packer successfully built the image. The image ID should match the SHA produced in the Packer output.

$ docker images
REPOSITORY                        TAG                          IMAGE ID       CREATED          SIZE
<none>                            <none>                       e763bc0ceb48   3 minutes ago    134MB
## ...

Modify template

Now that you have built an image using a HCL2 template, you will now add a provisioner and post-processor to your template.

First, add the following provisioner in the build block after the existing provisioner.

provisioner "shell" {
  environment_vars = [
    "FOO=hello world",
  ]
  inline = [
    "echo Adding file to Docker Container",
    "echo \"FOO is $FOO\" > example.txt",
  ]
}

This block defines a shell provisioner which sets an environment variable named FOO in the shell execution environment and runs the commands in the inline attribute. This provisioner will create a file named example.txt that contains FOO is hello world.

Next, add the following post-processor in the build block after the provisioner blocks.

post-processor "docker-tag" {
  repository = "learn-packer"
  tags       = ["ubuntu-xenial"]
}

This post-processor block will tag the newly created image with ubuntu-xenial.

After adding these provisioner and post-processor blocks, your build block should look like the following.

build {
  sources = ["source.docker.ubuntu"]

  provisioner "shell" {
    inline = ["echo Hello world!"]
  }

  provisioner "shell" {
    environment_vars = [
      "FOO=hello world",
    ]
    inline = [
      "echo Adding file to Docker Container",
      "echo \"FOO is $FOO\" > example.txt",
    ]
  }

  post-processor "docker-tag" {
    repository = "learn-packer"
    tags       = ["ubuntu-xenial"]
  }
}

Build modified Packer image

Build your image.

$ packer build .
docker.ubuntu: output will be in this color.

==> docker.ubuntu: Creating a temporary directory for sharing data...
==> docker.ubuntu: Pulling Docker image: ubuntu:xenial
    docker.ubuntu: xenial: Pulling from library/ubuntu
    docker.ubuntu: Digest: sha256:eed7e1076bbc1f342c4474c718e5438af4784f59a4e88ad687dbb98483b59ee4
    docker.ubuntu: Status: Image is up to date for ubuntu:xenial
    docker.ubuntu: docker.io/library/ubuntu:xenial
==> docker.ubuntu: Starting docker container...
    docker.ubuntu: Run command: docker run -v /Users/youruser/.packer.d/tmp051358398:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu:xenial
    docker.ubuntu: Container ID: e2e739467b24f21d113d37cf1dd709a8038e0f891cc6d34f56e50e229010d9e2
==> docker.ubuntu: Using docker communicator to connect: 172.17.0.3
==> docker.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell915176051
    docker.ubuntu: Hello world!
==> docker.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell946919744
    docker.ubuntu: Adding file to Docker Container
==> docker.ubuntu: Committing the container
    docker.ubuntu: Image ID: sha256:513c63d6a34abaf658faf709ad6e3bf24c07f232be1fec7e7aa2ef5a00f11921
==> docker.ubuntu: Killing the container: e2e739467b24f21d113d37cf1dd709a8038e0f891cc6d34f56e50e229010d9e2
==> docker.ubuntu: Running post-processor:  (type docker-tag)
    docker.ubuntu (docker-tag): Tagging image: sha256:513c63d6a34abaf658faf709ad6e3bf24c07f232be1fec7e7aa2ef5a00f11921
    docker.ubuntu (docker-tag): Repository: learn-packer:ubuntu-xenial
Build 'docker.ubuntu' finished after 19 seconds 647 milliseconds.

==> Wait completed after 19 seconds 648 milliseconds

==> Builds finished. The artifacts of successful builds are:
--> docker.ubuntu: Imported Docker image: sha256:513c63d6a34abaf658faf709ad6e3bf24c07f232be1fec7e7aa2ef5a00f11921
--> docker.ubuntu: Imported Docker image: learn-packer:ubuntu-xenial with tags learn-packer:ubuntu-xenial

In the output, you will find Adding file to Docker Container that confirms that the Packer ran the second provision block.

==> docker.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell946919744
    docker.ubuntu: Adding file to Docker Container

Verify Docker image

List the images to verify Packer successfully tagged the image.

$ docker images learn-packer
REPOSITORY     TAG                          IMAGE ID       CREATED         SIZE
learn-packer   ubuntu-xenial                513c63d6a34a   2 minutes ago   134MB

To verify the provisioner created the example.txt file in the new image, first launch the newly created Docker image. Replace IMAGE_ID with the value shown above in your terminal (your local equivalent of 513c63d6a34a as shown in the output above).

$ docker run -it IMAGE_ID

In the Docker container shell, print the contents of the example.txt file. This should return FOO is hello world as expected.

$ cat example.txt
FOO is hello world

Type exit to leave the container.

$ exit

Next steps

In this tutorial, you upgraded a Packer JSON template to HCL2. Then, you added provisioners and post processors to the upgraded HCL2 template.

Refer to the following resources for additional details on the concepts covered in this tutorial:

  • Read more about the hcl2_upgrade command and Packer HCL2 syntax.
  • Read more about the init command.
  • Learn how to provision infrastructure with Packer and Terraform.
  • Read more about why Packer switched from JSON to HCL2 in HashiCorp Packer with HCL Configs blog post.
 Back to Collection
 Next Collection

On this page

  1. Upgrade Packer JSON Template to HCL2
  2. Prerequisites
  3. Clone the sample repository
  4. Review Packer template
  5. Upgrade Packer JSON template to HCL2
  6. Update generated template
  7. Build Packer image
  8. Modify template
  9. Build modified Packer image
  10. Next steps
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)