github.com/hashicorp/packer@v1.14.3/website/content/guides/packer-on-cicd/pipelineing-builds.mdx (about)

     1  ---
     2  page_title: Packer Build Pipelines
     3  description: >-
     4    Learn how to shorten Packer build times and improve reliability. Start from an ISO, customize using the virtualbox-ovf builder, and improve efficiency.
     5  ---
     6  
     7  # Why Create a Template Pipeline?
     8  
     9  A common issue users face when beginning to create their Packer templates is
    10  that while they may need several specialized images, the early provisioning
    11  steps are all the same. It can feel tedious to copy all of those images' basic
    12  configuraton into each build template. It can feel even more tedious to wait a
    13  long time for similar builds to run duplicate steps.
    14  
    15  This is one reason why Packer recommends breaking your builds into small,
    16  discrete steps. Not only does it allow you to create "base" images that you can
    17  build from to create further customizations, but it also allows you to save
    18  time in your build process because the "base" images are likely to change less
    19  than your customizations.
    20  
    21  It also makes it so that a failing build takes less time to debug and re-run.
    22  
    23  In this example, we will use the Virtualbox builders, but the concepts from
    24  this example can be applied to other builders as well.
    25  
    26  ## Starting from an ISO
    27  
    28  Here is an extremely basic virtualbox-iso template:
    29  
    30  <Tabs>
    31  <Tab heading="HCL2">
    32  
    33  ```hcl
    34  source "virtualbox-iso" "step_1" {
    35    boot_command     = ["<esc><wait>", "<esc><wait>", "<enter><wait>",
    36                        "/install/vmlinuz<wait>", " initrd=/install/initrd.gz",
    37                        " auto-install/enable=true", " debconf/priority=critical",
    38                        " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ubuntu_preseed.cfg<wait>",
    39                        " -- <wait>", "<enter><wait>"]
    40    disk_size        = "40960"
    41    guest_os_type    = "Ubuntu_64"
    42    http_directory   = "./http"
    43    iso_checksum     = "sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2"
    44    iso_url          = "http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04-server-amd64.iso"
    45    shutdown_command = "echo 'vagrant' | sudo -S shutdown -P now"
    46    ssh_password     = "vagrant"
    47    ssh_port         = 22
    48    ssh_username     = "vagrant"
    49    vm_name          = "vbox-example"
    50  }
    51  build {
    52    sources = ["source.virtualbox-iso.step_1"]
    53  
    54  
    55    provisioner "shell" {
    56      inline = ["echo initial provisioning"]
    57    }
    58    post-processor "manifest" {
    59      output = "stage-1-manifest.json"
    60    }
    61  }
    62  ```
    63  
    64  </Tab>
    65  <Tab heading="JSON">
    66  
    67  ```json
    68  {
    69    "builders": [
    70      {
    71        "type": "virtualbox-iso",
    72        "vm_name": "vbox-example",
    73        "boot_command": [
    74          "<esc><wait>",
    75          "<esc><wait>",
    76          "<enter><wait>",
    77          "/install/vmlinuz<wait>",
    78          " initrd=/install/initrd.gz",
    79          " auto-install/enable=true",
    80          " debconf/priority=critical",
    81          " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ubuntu_preseed.cfg<wait>",
    82          " -- <wait>",
    83          "<enter><wait>"
    84        ],
    85        "http_directory": "./http",
    86  
    87        "disk_size": "40960",
    88        "guest_os_type": "Ubuntu_64",
    89        "iso_checksum": "sha256:946a6077af6f5f95a51f82fdc44051c7aa19f9cfc5f737954845a6050543d7c2",
    90        "iso_url": "http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04-server-amd64.iso",
    91  
    92        "shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now",
    93  
    94        "ssh_port": 22,
    95        "ssh_username": "vagrant",
    96        "ssh_password": "vagrant"
    97      }
    98    ],
    99    "provisioners": [
   100      {
   101        "type": "shell",
   102        "inline": ["echo initial provisioning"]
   103      }
   104    ],
   105    "post-processors": [
   106      {
   107        "type": "manifest",
   108        "output": "stage-1-manifest.json"
   109      }
   110    ]
   111  }
   112  ```
   113  
   114  </Tab>
   115  </Tabs>
   116  
   117  In order to build using this template, create a directory named "http" in your
   118  current working directory. Copy the minimal example from our
   119  [preseed guide](/packer/guides/automatic-operating-system-installs/preseed_ubuntu#examples)
   120  into a file in your http directory and name it "ubuntu_preseed.cfg". Copy the
   121  above json template into your current working directory and save it as
   122  "example_virtualbox_iso.json"
   123  
   124  To run the build, call `packer build example_virtualbox_iso.json`.
   125  
   126  This example does not set the output_directory or output_filename, so the file
   127  will be placed in a default name of "output-virtualbox-iso/vbox-example.ovf" --
   128  the builder will print this file name to the UI output, but in this example the
   129  [manifest](/packer/docs/post-processors/manifest) post-processor
   130  to will store build information, including the names of the output files, in a
   131  json file named "stage-1-manifest.json". From there, you can programmatically
   132  look up the output file information.
   133  
   134  ## Customizing the iso using the virtualbox-ovf builder
   135  
   136  That output filename generated in the first stage can be used as the
   137  [source_path](/packer/plugins/builders/virtualbox/ovf#source_path)
   138  for the virtualbox-ovf builder.
   139  
   140  <Tabs>
   141  <Tab heading="HCL2">
   142  
   143  ```hcl
   144  source "virtualbox-ovf" "step_2" {
   145    shutdown_command = "echo 'vagrant' | sudo -S shutdown -P now"
   146    source_path      = "output-virtualbox-iso/vbox-example.ovf"
   147    ssh_password     = "vagrant"
   148    ssh_port         = 22
   149    ssh_username     = "vagrant"
   150    vm_name          = "virtualbox-example-ovf"
   151  }
   152  
   153  build {
   154    sources = ["source.virtualbox-ovf.step_2"]
   155  
   156    provisioner "shell" {
   157      inline = ["echo secondary provisioning"]
   158    }
   159  }
   160  
   161  ```
   162  
   163  </Tab>
   164  <Tab heading="JSON">
   165  
   166  ```json
   167  {
   168    "builders": [
   169      {
   170        "type": "virtualbox-ovf",
   171        "vm_name": "virtualbox-example-ovf",
   172  
   173        "shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now",
   174        "source_path": "output-virtualbox-iso/vbox-example.ovf",
   175  
   176        "ssh_password": "vagrant",
   177        "ssh_port": 22,
   178        "ssh_username": "vagrant"
   179      }
   180    ],
   181    "provisioners": [
   182      {
   183        "inline": ["echo secondary provisioning"],
   184        "type": "shell"
   185      }
   186    ]
   187  }
   188  ```
   189  
   190  </Tab>
   191  </Tabs>
   192  
   193  ## More efficiencies
   194  
   195  You may find that you want to run time-consuming import post-processors like
   196  the "amazon-import" post-processor independently of the build that produces
   197  the artifacts you want to process.
   198  
   199  In this case, you can use a null builder
   200  and manually modify the input to the post-processing chain so that you can get
   201  the behavior you want. The below example shows a "vagrant" post-processor
   202  being used with a null builder, and manually sets the artifact from our
   203  stage-2 ovf build:
   204  
   205  <Tabs>
   206  <Tab heading="HCL2">
   207  
   208  ```hcl
   209  source "null" "step_3" {
   210    communicator = "none"
   211  }
   212  
   213  build {
   214    sources = ["source.null.step_3"]
   215  
   216    post-processors {
   217      post-processor "artifice" {
   218        files = ["output-virtualbox-ovf/virtualbox-example-ovf.ovf", "output-virtualbox-ovf/virtualbox-example-ovf-disk001.vmdk"]
   219      }
   220      post-processor "vagrant" {
   221        provider_override = "virtualbox"
   222      }
   223    }
   224  }
   225  ```
   226  
   227  </Tab>
   228  <Tab heading="JSON">
   229  
   230  ```json
   231  {
   232    "builders": [
   233      {
   234        "type": "null",
   235        "communicator": "none"
   236      }
   237    ],
   238    "post-processors": [
   239      [
   240        {
   241          "type": "artifice",
   242          "files": [
   243            "output-virtualbox-ovf/virtualbox-example-ovf.ovf",
   244            "output-virtualbox-ovf/virtualbox-example-ovf-disk001.vmdk"
   245          ]
   246        },
   247        {
   248          "type": "vagrant",
   249          "provider_override": "virtualbox"
   250        }
   251      ]
   252    ]
   253  }
   254  ```
   255  
   256  </Tab>
   257  </Tabs>
   258  
   259  By using the null builder instead of just running an ovf builder, we can spare
   260  ourselves all of the time Packer would normally spend launching and destroying
   261  VMs.
   262  
   263  ## Putting it all together
   264  
   265  Packer templates don't come with a custom "glue" to bind them together. We
   266  recommend using your CI system or wrapping scripts to connect the templates
   267  into a chain.
   268  
   269  ## Chaining together several of the same builders to make "save points"
   270  
   271  If you want to use the same builder for several builds in a row, this can feel
   272  tedious to implement in json. We recommend you try using HCL configs so that
   273  you can reuse the same source in several builds:
   274  
   275  HCL templates work by allowing you to draw sources and variables from multiple
   276  different files in a single directory, so the following files are assumed to
   277  exist in their own folder:
   278  
   279  sources.pkr.hcl
   280  
   281  ```hcl
   282  // In your sources file, you can create a configuration for a builder that you
   283  // want to reuse between multiple steps in the build. Just leave the source
   284  // and destination images out of this source, and set them specifically in each
   285  // step without having to set all of the other options over and over again.
   286  
   287  source "docker" "example" {
   288    commit = true
   289    // any other configuration you want for your Docker containers
   290  }
   291  ```
   292  
   293  build.pkr.hcl
   294  
   295  ```hcl
   296  build {
   297    // Make sure to name your builds so that you can selectively run them one at
   298    // a time.
   299    name = "step1"
   300  
   301    source "source.docker.example" {
   302      image = "ubuntu"
   303    }
   304  
   305    provisioner "shell" {
   306      inline = ["echo example provisioner"]
   307    }
   308    provisioner "shell" {
   309      inline = ["echo another example provisioner"]
   310    }
   311    provisioner "shell" {
   312      inline = ["echo a third example provisioner"]
   313    }
   314  
   315    // Make sure that the output from your build can be used in the next build.
   316    // In this example, we're tagging the Docker image so that the step-2
   317    // builder can find it without us having to track it down in a manifest.
   318    post-processor "docker-tag" {
   319      repository = "ubuntu"
   320      tag = ["step-1-output"]
   321    }
   322  }
   323  
   324  build {
   325      name = "step2"
   326  
   327      source "source.docker.example" {
   328        // This is the tagged artifact from the stage 1 build. You can retrieve
   329        // this from a manifest file and setting it as a variable on the command
   330        // line, or by making sure you define and know the output of the build,
   331        // if it's something you can define like an output name or directory.
   332        image = "ubuntu:step-1-output"
   333        // disable the pull if your image tag only exists locally
   334        pull = false
   335      }
   336  
   337      provisioner "shell" {
   338        inline = ["echo another provision!"]
   339      }
   340  }
   341  ```
   342  
   343  pipeline.sh
   344  
   345  ```sh
   346  #!/bin/bash
   347  set -e # abort if there is an issue with any build
   348  packer build -only='step1.docker.example' .
   349  packer build -only='step2.docker.example' .
   350  ```
   351  
   352  To run the pipeline, call pipeline.sh. You can create as many build steps as
   353  you want. Each can either inhabit one file, or you can put multiple steps in
   354  a single file like shown in the example above.