How to use Packer to create Ubuntu 18.04 Vagrant boxes

Create Ubuntu 18,04 Vagrant Boxes with Packer

Packer is a great tool that is used to create server images. Images can be created for a variety of platforms, such as AWS AMIs, VMware, VirtualBox, and Vagrant. This tutorial will guide you through creating Ubuntu 18.04 Vagrant boxes.

Prerequisites

You will need to install the latest versions of VirtualBox and Packer to follow along. You should also have Vagrant installed as well, to test the images you create.

  • Virtualbox
  • Vagrant
  • Packer

Tutorial Files on Github

I’ve created a Github repository to host the files used in this tutorial. Use this to view the entire workspace structure and file contents to better follow along.

https://github.com/serainville/packer_templates

Creating a Basic Image


Packer uses a JSON file to define your image. There are three main sections to the file: builders, provisions, preprocess.

Builders is what determines what kind of image you intend to create. This is where you will tell Packer that you are creating an AWS AMI or a Virtualbox OVF, which will be the case for this tutorial.

You are not limited to just a single builder. If you need an identical image to be used in AWS and Vagrant, for example, you can create several builders. Each builder is an array item in the Packer configuration file.

Provisioners is the next section of a Packer JSON file. After the operating system is installed provisioners are invoked to configure the system. There are a large number of options available, from your basic shell scripts to using Ansible playbooks or Puppet modules. The beauty of using these is you could create a mirror image of a production server for a Vagrant environment.

Lastly, we have our postprocessor. This is an optional step, but it is required for creating Vagrant boxes. Vagrant boxes are generated by taking a Virtualbox OVF and packaging it as a Vagrant box image.

Other postprocessor options allow you to compress your image.

Adding A Virtualbox Builder

A Virtualbox image will be the base of our Vagrant box. After we create our image we’ll then use a post-processing step to package the image for Vagrant.

  1. Create a workspace for your Packer configurations. A typical directory structure for Packer is as follows.
    Packer/
          |---ubuntu1804.json
          |---http/
          |       |--- preseed.cfg
          |---scripts/
                  |--- init.sh
                  |--- cleanup.sh
    
    
  2. Create a new JSON file for the Ubuntu 18.04 image. In the directory structure above we name our file ubuntu1804.json.
  3. Open the JSON file into a text editor. Then add the following lines.
    {
        "builders": [
            {
                "type": "virtualbox-iso",
                "boot_command": [
                "",
                "",
                "",
                "/install/vmlinuz",
                " auto",
                " console-setup/ask_detect=false",
                " console-setup/layoutcode=us",
                " console-setup/modelcode=pc105",
                " debconf/frontend=noninteractive",
                " debian-installer=en_US",
                " fb=false",
                " initrd=/install/initrd.gz",
                " kbd-chooser/method=us",
                " keyboard-configuration/layout=USA",
                " keyboard-configuration/variant=USA",
                " locale=en_US",
                " netcfg/get_domain=vm",
                " netcfg/get_hostname=vagrant",
                " grub-installer/bootdev=/dev/sda",
                " noapic",
                " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg",
                " -- ",
                ""
                ],
                "boot_wait": "10s",
                "disk_size": 81920,
                "guest_os_type": "Ubuntu_64",
                "headless": false,
                "http_directory": "http",
                "iso_urls": [
                "iso/ubuntu-18.04-server-amd64.iso",
                "http://cdimage.ubuntu.com/ubuntu/releases/bionic/release/ubuntu-18.04-server-amd64.iso"
                ],
                "iso_checksum_type": "sha256",
                "iso_checksum": "a7f5c7b0cdd0e9560d78f1e47660e066353bb8a79eb78d1fc3f4ea62a07e6cbc",
                "ssh_username": "vagrant",
                "ssh_password": "vagrant",
                "ssh_port": 22,
                "ssh_wait_timeout": "10000s",
                "shutdown_command": "echo 'vagrant'|sudo -S shutdown -P now",
                "guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso",
                "virtualbox_version_file": ".vbox_version",
                "vm_name": "packer-ubuntu-18.04-amd64",
                "vboxmanage": [
                [
                    "modifyvm",
                    "{{.Name}}",
                    "--memory",
                    "1024"
                ],
                [
                    "modifyvm",
                    "{{.Name}}",
                    "--cpus",
                    "1"
                ]
                ]
            }
        ],
        "provisioners": [{
          "type": "shell",
          "scripts": [
            "scripts/init.sh",
            "scripts/cleanup.sh"
          ]
        }],
        "post-processors": [{
          "type": "vagrant",
          "compression_level": "8",
          "output": "ubuntu-18.04-{{.Provider}}.box"
        }]
      }
  4. Save your changes and exit the text editor.

Understanding the Packer JSON File for Ubuntu 18.04

That’s a lot of configuration to digest. Let’s break down each part and explain what everything does. This is just the basics for a Virtualbox OVF image. If we weren’t interested in creating a Vagrant box we would be done.

"builders": [...]

As was mentioned earlier, we can specify a number of builders within a JSON array. We are only creating a single builder for creating a Virtualbox image.

"type": "virtualbox-iso",

The most important setting for a builder is its type. This is what tells Packer how to create the image. Next, we have our build boot commands. These automate the selection of the boot options, such as language, keyboard layout, etc.

Packer needs access to an ISO of Ubuntu when creating Virtualbox images. The ISO can be local or downloaded from the web. Multiple sources can be added. In our example below, we are creating two sources: one in a local iso directory, relative to the JSON file, and a URL to the official ISO hosted by Ubuntu.

"iso_urls": [
     "iso/ubuntu-18.04-server-amd64.iso",
     "http://cdimage.ubuntu.com/ubuntu/releases/bionic/release/ubuntu-18.04-server-amd64.iso"
],

Packer forces you to specify a checksum value for the ISO. This is to ensure the image hasn’t been modified. When creating an image for your servers, whether for production or just, you want to be confident no one has added a logger or rootkit onto it.

"iso_checksum_type": "sha256",
"iso_checksum": "a7f5c7b0cdd0e9560d78f1e47660e066353bb8a79eb78d1fc3f4ea62a07e6cbc",

The checksum type we’re using is SHA256 and the expected value is set using iso_checksum.

"boot_command": [...]

Inside the boot command we have the following:

"<esc><wait>",
"<esc><wait>",
"<esc><wait>",
"/install/vmlinuz<wait>",
" auto<wait>",
" console-setup/ask_detect=false<wait>",
" console-setup/layoutcode=us<wait>",
" console-setup/modelcode=pc105<wait>",
" debconf/frontend=noninteractive<wait>",
" debian-installer=en_US<wait>",
" fb=false<wait>",
" initrd=/install/initrd.gz<wait>",
" kbd-chooser/method=us<wait>",
" keyboard-configuration/layout=USA<wait>",
" keyboard-configuration/variant=USA<wait>",
" locale=en_US<wait>",
" netcfg/get_domain=vm<wait>",
" netcfg/get_hostname=vagrant<wait>",
" grub-installer/bootdev=/dev/sda<wait>",
" noapic<wait>",
" preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ubuntu1804-preseed.cfg<wait>",
" -- <wait>",
"<enter><wait>"

 

 

Provisioners

Here’s where the real work begins. Having a vanilla OS image helps, but it’s more useful to have an image for a particular server role ready to be deployed.  This could be a LAMP development server, a NodeJS server, or a Docker host. It’s whatever you need to spin up frequently and quickly.

There are a few provisioning options available. Good ‘ole bash scripts can be used. Alternatively, configuration management tools such as Ansible, Puppet, and Chef can be used, allowing you to utilize your already existing configurations.

Provisioning Boxes using Scripts

The scripts provisioner will copy your scripts to the new virtualbox vm. You will need to tell Packer which scripts to copy, and then they will be executed in sequence. Create a new script called init.sh under your scripts directory. Add the following contents to it.

#!/bin/bash

sudo apt update
sudo apt upgrade -y

The script above will update the APT cache and then install any patches available.  Let’s add it to our ubuntu1804.json file, under the builders section.

    "provisioners": [{
      "type": "shell",
      "scripts": [
        "scripts/init.sh",
        "scripts/lamp.sh"
      ]
    }],

Provisioning Boxes Using Ansible

This tutorial uses shell scripts, but Packer supports Ansible, too. You can tell Packer to provision using your playbooks using the following provision example.

"provisioners": [
  {
    "type": "ansible",
    "playbook_file": "./playbook.yml"
  }
],

 

Vagrant Post Processor

Up until now all we are able to output is a Virtualbox image. Our goal, however, is to have a Vagrantbox. This is where the Packer post processor comes into play. Once the Virtualbox image is created the post processor will package the image in a Vagrant box, which is essentially an OVF file with metadata compressed into an archive file.

"post-processors": [{
  "type": "vagrant",
  "compression_level": "6",
  "output": "ubuntu-18.04-{{.Provider}}.box"
}],

The options are fairly self-explanatory. We need to specify the type as Vagrant. The compression level we want to tar the package with, and finally the outputted filename.

Building our Vagrant Image

We’ve added all of the necessary bits to our Packer file. Let’s create our image. Run the following command from the directory our JSON file is located.

packer build ubuntu1804.json

 

Adding Our New Box to Vagrant

Our box is built and ready for use. The final step needed is to add the box to Vagrant. Most boxes are pulled down from the Internet, but we need to add a local box. The following command will add your new box to Vagrant.

vagrant box add ubuntu1804-vagrant.box

To use it in your Vagrantfile, use the name you specified in the builder’s section. In our example, we called our box ubuntu1804-vagrant. The following is an example Vagrantfile that uses our new box.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.box = "ubuntu1804-vagarant"

  config.vm.network "forwarded_port", guest: 80, host: 8080

  config.vm.provider "virtualbox" do |vb|
    # Display the VirtualBox GUI when booting the machine
    vb.gui = false
  end

end