»Custom Builders

Packer Builders are the components of Packer responsible for creating a machine, bringing it to a point where it can be provisioned, and then turning that provisioned machine into some sort of machine image. Several builders are officially distributed with Packer itself, such as the AMI builder, the VMware builder, etc. However, it is possible to write custom builders using the Packer plugin interface, and this page documents how to do that.

Prior to reading this page, it is assumed you have read the page on plugin development basics.

»The Interface

The interface that must be implemented for a builder is the packer.Builder interface. It is reproduced below for reference. The actual interface in the source code contains some basic documentation as well explaining what each method should do.

type Builder interface {
  ConfigSpec() hcldec.ObjectSpec
  Prepare(...interface{}) ([]string, []string, error)
  Run(context.Context, ui Ui, hook Hook) (Artifact, error)

»The "ConfigSpec" Method

This method returns a hcldec.ObjectSpec, which is a spec necessary for using HCL2 templates with Packer. For information on how to use and implement this function, check our object spec docs

»The "Prepare" Method

The Prepare method for each builder is called prior to any runs with the configuration that was given in the template. This is passed in as an array of interface{} types, but is generally map[string]interface{}. The prepare method is responsible for translating this configuration into an internal structure, validating it, and returning any errors.

For multiple parameters, they should be merged together into the final configuration, with later parameters overwriting any previous configuration. The exact semantics of the merge are left to the builder author.

For decoding the interface{} into a meaningful structure, the mapstructure library is recommended. Mapstructure will take an interface{} and decode it into an arbitrarily complex struct. If there are any errors, it generates very human friendly errors that can be returned directly from the prepare method.

While it is not actively enforced, no side effects should occur from running the Prepare method. Specifically, don't create files, don't launch virtual machines, etc. Prepare's purpose is solely to configure the builder and validate the configuration.

In addition to normal configuration, Packer will inject a map[string]interface{} with a key of packer.DebugConfigKey set to boolean true if debug mode is enabled for the build. If this is set to true, then the builder should enable a debug mode which assists builder developers and advanced users to introspect what is going on during a build. During debug builds, parallelism is strictly disabled, so it is safe to request input from stdin and so on.

»The "Run" Method

Run is where all the interesting stuff happens. Run is executed, often in parallel for multiple builders, to actually build the machine, provision it, and create the resulting machine image, which is returned as an implementation of the packer.Artifact interface.

The Run method takes three parameters. These are all very useful. The packer.Ui object is used to send output to the console. packer.Hook is used to execute hooks, which are covered in more detail in the hook section below. And packer.Cache is used to store files between multiple Packer runs, and is covered in more detail in the cache section below.

Because builder runs are typically a complex set of many steps, the multistep package is recommended to bring order to the complexity. Multistep is a library which allows you to separate your logic into multiple distinct "steps" and string them together. It fully supports cancellation mid-step and so on. Please check it out, it is how the built-in builders are all implemented.

Finally, as a result of Run, an implementation of packer.Artifact should be returned. More details on creating a packer.Artifact are covered in the artifact section below. If something goes wrong during the build, an error can be returned, as well. Note that it is perfectly fine to produce no artifact and no error, although this is rare.


The Run method is often run in parallel.

»With the "Cancel" Method ( up until packer 1.3 )

The Cancel method can be called at any time and requests cancellation of any builder run in progress. This method should block until the run actually stops. Not that the Cancel method will no longer be called since packer 1.4.0.

»Context cancellation ( from packer 1.4 )

The <-ctx.Done() can unblock at any time and signifies request for cancellation of any builder run in progress.

Cancels are most commonly triggered by external interrupts, such as the user pressing Ctrl-C. Packer will only exit once all the builders clean up, so it is important that you architect your builder in a way that it is quick to respond to these cancellations and clean up after itself.

»Creating an Artifact

The Run method is expected to return an implementation of the packer.Artifact interface. Each builder must create their own implementation. The interface has ample documentation to help you get started.

The only part of an artifact that may be confusing is the BuilderId method. This method must return an absolutely unique ID for the builder. In general, I follow the practice of making the ID contain my GitHub username and then the platform it is building for. For example, the builder ID of the VMware builder is "hashicorp.vmware" or something similar.

Post-processors use the builder ID value in order to make some assumptions about the artifact results, so it is important it never changes.

Other than the builder ID, the rest should be self-explanatory by reading the packer.Artifact interface documentation.


Packer has built-in support for provisioning, but the moment when provisioning runs must be invoked by the builder itself, since only the builder knows when the machine is running and ready for communication.

When the machine is ready to be provisioned, run the packer.HookProvision hook, making sure the communicator is not nil, since this is required for provisioners. An example of calling the hook is shown below:

hook.Run(context.Context, packer.HookProvision, ui, comm, nil)

At this point, Packer will run the provisioners and no additional work is necessary.

»Template Engine

»Build variables

Packer makes it possible to provide custom template engine variables to be shared with provisioners and post-processors using the build function.

Part of the builder interface changes made in 1.5.0 was to make builder Prepare() methods return a list of custom variables which we call generated data. We use that list of variables to generate a custom placeholder map per builder that combines custom variables with the placeholder map of default build variables created by Packer. Here's an example snippet telling packer what will be made available by the builder:

func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
    // ...

    generatedData := []string{"SourceImageName"}
    return generatedData, warns, nil

Returning the custom variable name(s) within the generated_data placeholder is necessary for the template containing the build variable(s) to validate.

Once the placeholder is set, it's necessary to pass the variables' real values when calling the provisioner. This can be done as the example below:

func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
    // ...

    // Create map of custom variable
    generatedData := map[string]interface{}{"SourceImageName": "the source image name value"}
    // Pass map to provisioner
    hook.Run(context.Context, packer.HookProvision, ui, comm, generatedData)

    // ...

In order to make these same variables and the Packer default ones also available to post-processor, it is necessary to add them to the Artifact returned by the builder. This can be done by adding an attribute of type map[string]interface{} to the Artifact and putting the generated data in it. The post-processor will access this data later via the Artifact's State method.

The Artifact code should be implemented similar to the below:

type Artifact struct {
    // ...

    // StateData should store data such as GeneratedData
    // to be shared with post-processors
    StateData map[string]interface{}

// ...

func (a *Artifact) State(name string) interface{} {
    return a.StateData[name]

// ...

The builder should return the above Artifact containing the generated data and the code should be similar to the example snippet below:

func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
    // ...

    return &Artifact{
                // ...
                StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
            }, nil

The code above assigns the generated_data state to the StateData map with the key generated_data.

Here some example of how this data will be used by post-processors:

func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, source packer.Artifact) (packer.Artifact, bool, bool, error) {
    generatedData := source.State("generated_data")

    // generatedData will then be used for interpolation

    // ...

To know more about the template engine build function, please refer to the template engine docs.