» JSON Configuration Syntax

Most Packer configurations are written in the native HCL syntax, which is designed to be easy for humans to read and update.

Packer also supports an alternative syntax that is JSON-compatible. This syntax is useful when generating portions of a configuration programmatically, since existing JSON libraries can be used to prepare the generated configuration files.

The JSON syntax is defined in terms of the native syntax. Everything that can be expressed in native syntax can also be expressed in JSON syntax, but some constructs are more complex to represent in JSON due to limitations of the JSON grammar.

Packer expects native syntax for files named with a .pkr.hcl suffix, and JSON syntax for files named with a .pkr.json suffix.

The low-level JSON syntax, just as with the native syntax, is defined in terms of a specification called HCL. It is not necessary to know all of the details of HCL syntax or its JSON mapping in order to use Packer, and so this page summarizes the most important differences between native and JSON syntax. If you are interested, you can find a full definition of HCL's JSON syntax in its specification.

» JSON File Structure

At the root of any JSON-based Packer configuration is a JSON object. The properties of this object correspond to the top-level block types of the Packer language. For example:

{
  "variables": {
    "example": "value"
  }
}

Each top-level object property must match the name of one of the expected top-level block types. Block types that expect labels, such as variable shown above, are represented by one nested object value for each level of label. source blocks expect two labels, so two levels of nesting are required:

{
  "source": {
    "amazon-ebs": {
      "example": {
        "instance_type": "t2.micro",
        "ami_name": "ami-abc123"
      }
    }
  }
}

After any nested objects representing the labels, finally one more nested object represents the body of the block itself. In the above example the instance_type and ami_name arguments for source "amazon-ebs" "example" are specified.

Taken together, the above two configuration files are equivalent to the following blocks in the native syntax:

variables {
  example = "value"
}

source "amazon-ebs" "example" {
  instance_type = "t2.micro"
  ami_name      = "ami-abc123"
}

Within each top-level block type the rules for mapping to JSON are slightly different (see [Block-type-specific Exceptions][inpage-exceptions] below), but the following general rules apply in most cases:

  • The JSON object representing the block body contains properties that correspond either to argument names or to nested block type names.

  • Where a property corresponds to an argument that accepts arbitrary expressions in the native syntax, the property value is mapped to an expression as described under Expression Mapping below. For arguments that do not accept arbitrary expressions, the interpretation of the property value depends on the argument, as described in the block-type-specific exceptions given later in this page.

  • Where a property name corresponds to an expected nested block type name, the value is interpreted as described under Nested Block Mapping below, unless otherwise stated in the block-type-specific exceptions given later in this page.

» Expression Mapping

Since JSON grammar is not able to represent all of the Packer language expression syntax, JSON values interpreted as expressions are mapped as follows:

JSON Packer Language Interpretation
Boolean A literal bool value.
Number A literal number value.
String Parsed as a string template and then evaluated as described below.
Object Each property value is mapped per this table, producing an object(...) value with suitable attribute types.
Array Each element is mapped per this table, producing a tuple(...) value with suitable element types.
Null A literal null.

When a JSON string is encountered in a location where arbitrary expressions are expected, its value is first parsed as a string template and then it is evaluated to produce the final result.

If the given template consists only of a single interpolation sequence, the result of its expression is taken directly, without first converting it to a string. This allows non-string expressions to be used within the JSON syntax.

» Nested Block Mapping

When a JSON object property is named after a nested block type, the value of this property represents one or more blocks of that type. The value of the property must be either a JSON object or a JSON array.

The simplest situation is representing only a single block of the given type when that type expects no labels, as with the tags nested block used within source blocks:

{
  "source": {
    "amazon-ebs": {
      "example": {
        "tags": {
          "key": "value"
        }
      }
    }
  }
}

The above is equivalent to the following native syntax configuration:

source "amazon-ebs" "example" {
  tags {
    key = "value"
  }
}

When the nested block type requires one or more labels, or when multiple blocks of the same type can be given, the mapping gets a little more complicated. For example, the provisioner nested block type used within source blocks expects a label giving the provisioner to use, and the ordering of provisioner blocks is significant to decide the order of operations.

The following native syntax example shows a source block with a number of provisioners of different types:

source "amazon-ebs" "example" {
  # (source configuration omitted for brevity)

  provisioner "shell-local" {
    inline = ["echo 'Hello World' >example.txt"]
  }
  provisioner "file" {
    source      = "example.txt"
    destination = "/tmp/example.txt"
  }
  provisioner "shell" {
    inline = [
      "sudo install-something -f /tmp/example.txt",
    ]
  }
}

In order to preserve the order of these blocks, you must use a JSON array as the direct value of the property representing this block type, as in this JSON equivalent of the above:

{
  "source": {
    "amazon-ebs": {
      "example": {
        "provisioner": [
          {
            "shell-local": {
              "inline": ["echo 'Hello World' >example.txt"]
            }
          },
          {
            "file": {
              "source": "example.txt",
              "destination": "/tmp/example.txt"
            }
          },
          {
            "shell": {
              "inline": ["sudo install-something -f /tmp/example.txt"]
            }
          }
        ]
      }
    }
  }
}

Each element of the provisioner array is an object with a single property whose name represents the label for each provisioner block. For block types that expect multiple labels, this pattern of alternating array and object nesting can be used for each additional level.

If a nested block type requires labels but the order does not matter, you may omit the array and provide just a single object whose property names correspond to unique block labels. This is allowed as a shorthand for the above for simple cases, but the alternating array and object approach is the most general. We recommend using the most general form if systematically converting from native syntax to JSON, to ensure that the meaning of the configuration is preserved exactly.

» Comment Properties

Although we do not recommend hand-editing of JSON syntax configuration files -- this format is primarily intended for programmatic generation and consumption -- a limited form of comments are allowed inside JSON objects that represent block bodies using a special property name:

{
  "source": {
    "amazon-ebs": {
      "example": {
        "//": "This instance runs the scheduled tasks for backup",

        "instance_type": "t2.micro",
        "ami_name": "ami-abc123"
      }
    }
  }
}

In any object that represents a block body, properties named "//" are ignored by Packer entirely. This exception does not apply to objects that are being interpreted as expressions, where this would be interpreted as an object type attribute named "//".

This special property name can also be used at the root of a JSON-based configuration file. This can be useful to note which program created the file.

{
  "//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!",
}