Packer can generate a "golden image" of your infrastructure, and Chef can define that infrastructure’s desired state.

Let’s say you want to build a standard Ubuntu 22.04 AMI for your AWS environment, pre-configured with Nginx and a specific nginx.conf. Packer will orchestrate the build, and Chef will handle the configuration.

Here’s a Packer template (ubuntu-nginx.pkr.hcl) to get us started:

source "amazon-ebs" "ubuntu" {

  ami_name      = "ubuntu-nginx-{{timestamp}}"

  instance_type = "t3.micro"
  region        = "us-east-1"
  source_ami_filter {
    filters = {
      Name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
      Root-Device-Type = "ebs"
    }
    most_recent = true
    owners      = ["099720109477"] # Canonical's owner ID
  }
  ssh_username = "ubuntu"
}

build {
  sources = ["source.amazon-ebs.ubuntu"]

  provisioner "chef-client" {
    config_dir    = "chef-config"
    run_list      = ["recipe[nginx::default]"]
  }
}

This template tells Packer to:

  1. Use an amazon-ebs builder.
  2. Name the resulting AMI ubuntu-nginx- followed by a timestamp.
  3. Use a t3.micro instance type for the build.
  4. Specify the us-east-1 region.
  5. Find the most recent Ubuntu 22.04 (Jammy) LTS AMI from Canonical.
  6. Connect to the instance using the ubuntu user.
  7. Crucially, use the chef-client provisioner.
  8. Point to a chef-config directory.
  9. Execute a Chef run list that includes recipe[nginx::default].

Now, we need to define that nginx::default recipe. Create a chef-config directory next to your Packer template. Inside chef-config, create a cookbooks directory. Inside cookbooks, create an nginx directory. And inside nginx, create a recipes directory.

Your chef-config/cookbooks/nginx/recipes/default.rb would look like this:

package 'nginx' do
  action :install
end

service 'nginx' do
  supports status: true
  action [:enable, :start]
end

cookbook_file '/etc/nginx/nginx.conf' do
  source 'nginx.conf' # This will look for nginx.conf in the cookbook's files/default directory
  owner 'root'
  group 'root'
  mode '0644'
  notifies :reload, 'service[nginx]', :delayed
end

This Chef recipe will:

  1. Install the nginx package.
  2. Ensure the nginx service is enabled and started.
  3. Copy a custom nginx.conf file into place.

For the cookbook_file resource to work, you need to create a files/default directory inside your chef-config/cookbooks/nginx directory. Place your custom nginx.conf file there.

# Example custom nginx.conf
# chef-config/cookbooks/nginx/files/default/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

To run this, you’d execute:

packer init .
packer build .

Packer will launch an EC2 instance, bootstrap it with Chef, run the specified Chef recipes, and then create an AMI from that instance. The chef-client provisioner handles downloading and running Chef on the temporary build instance. Packer automatically uploads your chef-config directory to the instance for Chef to use.

The most surprising truth is that Packer’s chef-client provisioner doesn’t require a full Chef Server or even Chef Solo to be pre-installed on the base AMI. Packer handles bootstrapping the instance with a minimal Chef client that can then fetch and run your cookbook code, treating your local chef-config directory as its "cookbooks path" for the duration of the build. It effectively makes your local cookbooks available to the temporary build instance.

After your AMI is built, the next step is to launch EC2 instances from it and verify that Nginx is running and configured correctly, perhaps by testing the custom nginx.conf with curl.

Want structured learning?

Take the full Packer course →