Build a Windows image
You can use Packer to build Amazon Machine Images (AMIs) for any supported operating system. In this tutorial, you will use an existing AMI managed by AWS as the base image for your own AMI that you will customize using scripts and build templates. By building machine images to include your team's required tools and system settings, you can shorten the time it takes to deploy new instances.
Prerequisites
This tutorial assumes that you are familiar with the Packer workflow. If you are new to Packer, complete the Get Started tutorials first.
For this tutorial, you will need:
- Packer 1.7.10+ installed locally.
- An AWS account with credentials configured for Packer. Your user must have permissions to create, modify and delete EC2
instances. Refer to the
documentation
to find the full list IAM permissions required to run the
amazon-ebs
builder.
Tip
AWS charges for storing AMIs and any snapshots. Deregister the AMI and delete its snapshots at the end of the tutorial to avoid incurring unnecessary charges.
Clone repository
Clone the example repository for this tutorial, which contains Packer templates to build a Windows AMI.
$ git clone https://github.com/hashicorp-education/learn-packer-windows-ami
Change into the repository directory.
$ cd learn-terraform-packer-windows-ami
Review configuration
This configuration modifies an existing Windows base image managed by AWS. To build your AMI, Packer launches a build instance that runs the base image, connects to it to execute your build scripts, then takes a snapshot of your instance to create the AMI.
The Windows base image does not automatically allow ingress traffic, which Packer requires in order to connect to the instance. To work around this, the configuration specifies a user data script that will run on the build instance at launch and allow connections over WinRM.
Open bootstrap_win.txt
to review the user data script.
Warning
If you enable WinRM to customize your images, be sure to disable it or restrict its permissions in your Packer shutdown script to secure your instance.
bootstrap_win.txt
<powershell>
# Set administrator password
net user Administrator SuperS3cr3t!!!!
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
# Disable group policies which block basic authentication and unencrypted login
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'
# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM
</powershell>
The <powershell>
and </powershell>
tags at the top and bottom of the file
instruct the instance to execute the script using PowerShell. You can also use
<script></script>
tags to enclose any commands that you would normally run in
a Command Prompt window. This script configures the build instance and the services
running on it.
Review the AWS documentation on running commands in Windows instances at launch to learn more about the startup script.
Now, open the windows.pkr.hcl
file to review the build template that instructs Packer how to build your new AMI.
First, the template defines the required plugins and versions, an input variable for the region, and a local variable for the build timestamp.
windows.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.2.6"
source = "github.com/hashicorp/amazon"
}
}
}
variable "region" {
type = string
default = "us-east-1"
}
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
The configuration configures an Amazon EBS builder and uses the
source_ami_filter
to select an Amazon-managed base image for your build. It
also configures the instance to use your custom boot script as the
user_data_file
to enable Packer connections over WinRM.
windows.pkr.hcl
source "amazon-ebs" "firstrun-windows" {
ami_name = "packer-windows-demo-${local.timestamp}"
communicator = "winrm"
instance_type = "t2.micro"
region = "${var.region}"
source_ami_filter {
filters = {
name = "Windows_Server-2012-R2*English-64Bit-Base*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["amazon"]
}
user_data_file = "./bootstrap_win.txt"
winrm_password = "SuperS3cr3t!!!!"
winrm_username = "Administrator"
}
Next, the build template uses provisioners to customize your image.
windows.pkr.hcl
build {
name = "learn-packer"
sources = ["source.amazon-ebs.firstrun-windows"]
provisioner "powershell" {
environment_vars = ["DEVOPS_LIFE_IMPROVER=PACKER"]
inline = ["Write-Host \"HELLO NEW USER; WELCOME TO $Env:DEVOPS_LIFE_IMPROVER\"", "Write-Host \"You need to use backtick escapes when using\"", "Write-Host \"characters such as DOLLAR`$ directly in a command\"", "Write-Host \"or in your own scripts.\""]
}
provisioner "windows-restart" {
}
provisioner "powershell" {
environment_vars = ["VAR1=A$Dollar", "VAR2=A`Backtick", "VAR3=A'SingleQuote", "VAR4=A\"DoubleQuote"]
script = "./sample_script.ps1"
}
}
Note the three different provisioners:
- The first
powershell
provisioner uses theinline
argument to execute shorter snippets of code. - The
windows-restart
provisioner triggers a reboot, which may be necessary in your build scripts for changes to take effect. - The second
powershell
provisioner uses ascript
instead ofinline
, which is convenient if you have more complex or extensive customizations to apply.
Open sample_script.ps1
to review an example provisioner script.
sample_script.ps1
Write-Host "PACKER_BUILD_NAME is an env var Packer automatically sets for you."
Write-Host "...or you can set it in your builder variables."
Write-Host "The default for this builder is:" $Env:PACKER_BUILD_NAME
Write-Host "The PowerShell provisioner will automatically escape characters"
Write-Host "considered special to PowerShell when it encounters them in"
Write-Host "your environment variables or in the PowerShell elevated"
Write-Host "username/password fields."
Write-Host "For example, VAR1 from our config is:" $Env:VAR1
Write-Host "Likewise, VAR2 is:" $Env:VAR2
Write-Host "VAR3 is:" $Env:VAR3
Write-Host "Finally, VAR4 is:" $Env:VAR4
Write-Host "None of the special characters needed escaping in the template"
This script prints out the variable values passed in by the builder.
Build the AMI
Build your new AMI by running packer build windows.pkr.hcl
. Packer will use your AWS credentials to authenticate and build your image. This process may take up to 30 minutes to complete.
$ packer build windows.pkr.hcl
learn-packer.amazon-ebs.firstrun-windows: output will be in this color.
==> learn-packer.amazon-ebs.firstrun-windows: Prevalidating any provided VPC information
==> learn-packer.amazon-ebs.firstrun-windows: Prevalidating AMI Name: packer-windows-demo-20230723141211
learn-packer.amazon-ebs.firstrun-windows: Found Image ID: ami-0e8f9dba7c9dc0d62
==> learn-packer.amazon-ebs.firstrun-windows: Creating temporary keypair: packer_64bd353b-ef9a-a173-a98b-a8c0f6321d14
==> learn-packer.amazon-ebs.firstrun-windows: Creating temporary security group for this instance: packer_64bd354c-7f08-7959-9e3a-ec4ade09b5e5
==> learn-packer.amazon-ebs.firstrun-windows: Authorizing access to port 5985 from [0.0.0.0/0] in the temporary security groups...
==> learn-packer.amazon-ebs.firstrun-windows: Launching a source AWS instance...
learn-packer.amazon-ebs.firstrun-windows: Instance ID: i-04676a0e1df1e5931
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for instance (i-04676a0e1df1e5931) to become ready...
==> learn-packer.amazon-ebs.firstrun-windows: Skipping waiting for password since WinRM password set...
==> learn-packer.amazon-ebs.firstrun-windows: Using WinRM communicator to connect: 3.89.250.214
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for WinRM to become available...
learn-packer.amazon-ebs.firstrun-windows: WinRM connected.
==> learn-packer.amazon-ebs.firstrun-windows: Connected to WinRM!
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with Powershell...
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with powershell script: /var/folders/rb/96hwxg9n74b2bkhd34rxffq80000gp/T/powershell-provisioner1777896687
learn-packer.amazon-ebs.firstrun-windows: HELLO NEW USER; WELCOME TO PACKER
learn-packer.amazon-ebs.firstrun-windows: You need to use backtick escapes when using
learn-packer.amazon-ebs.firstrun-windows: characters such as DOLLAR$ directly in a command
learn-packer.amazon-ebs.firstrun-windows: or in your own scripts.
==> learn-packer.amazon-ebs.firstrun-windows: Restarting Machine
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for machine to restart...
learn-packer.amazon-ebs.firstrun-windows: WIN-ETSKAUE79QF restarted.
==> learn-packer.amazon-ebs.firstrun-windows: Machine successfully restarted, moving on
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with Powershell...
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with powershell script: ./sample_script.ps1
learn-packer.amazon-ebs.firstrun-windows: PACKER_BUILD_NAME is an env var Packer automatically sets for you.
learn-packer.amazon-ebs.firstrun-windows: ...or you can set it in your builder variables.
learn-packer.amazon-ebs.firstrun-windows: The default for this builder is: firstrun-windows
learn-packer.amazon-ebs.firstrun-windows: The PowerShell provisioner will automatically escape characters
learn-packer.amazon-ebs.firstrun-windows: considered special to PowerShell when it encounters them in
learn-packer.amazon-ebs.firstrun-windows: your environment variables or in the PowerShell elevated
learn-packer.amazon-ebs.firstrun-windows: username/password fields.
learn-packer.amazon-ebs.firstrun-windows: For example, VAR1 from our config is: A$Dollar
learn-packer.amazon-ebs.firstrun-windows: Likewise, VAR2 is: A`Backtick
learn-packer.amazon-ebs.firstrun-windows: VAR3 is: A'SingleQuote
learn-packer.amazon-ebs.firstrun-windows: Finally, VAR4 is: A"DoubleQuote
learn-packer.amazon-ebs.firstrun-windows: None of the special characters needed escaping in the template
==> learn-packer.amazon-ebs.firstrun-windows: Stopping the source instance...
learn-packer.amazon-ebs.firstrun-windows: Stopping instance
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for the instance to stop...
==> learn-packer.amazon-ebs.firstrun-windows: Creating AMI packer-windows-demo-20230723141211 from instance i-04676a0e1df1e5931
learn-packer.amazon-ebs.firstrun-windows: AMI: ami-0af7c046a2c531c5e
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for AMI to become ready...
==> learn-packer.amazon-ebs.firstrun-windows: Skipping Enable AMI deprecation...
==> learn-packer.amazon-ebs.firstrun-windows: Terminating the source AWS instance...
==> learn-packer.amazon-ebs.firstrun-windows: Cleaning up any extra volumes...
==> learn-packer.amazon-ebs.firstrun-windows: No volumes to clean up, skipping
==> learn-packer.amazon-ebs.firstrun-windows: Deleting temporary security group...
==> learn-packer.amazon-ebs.firstrun-windows: Deleting temporary keypair...
Build 'learn-packer.amazon-ebs.firstrun-windows' finished after 22 minutes 26 seconds.
==> Wait completed after 22 minutes 26 seconds
==> Builds finished. The artifacts of successful builds are:
--> learn-packer.amazon-ebs.firstrun-windows: AMIs were created:
us-east-1: ami-0af7c046a2c531c5e
Next, navigate to your AMI dashboard to confirm Packer created your new AMI.
Deregister AMI
To limit incurring charges, deregister your AMI in the AWS console. Select the new AMI, then select Deregister AMI from the Actions dropdown menu.
Then, delete any associated snapshots.
Next steps
In this tutorial, you used Packer to build a customized Windows AMI. You can replicate this pattern to build and customize AMIs with the tools and settings required by your team.
Review the following resources to learn more about building images with Packer:
- Review the build block documentation to review the options available for AMI customization.
- Learn how to build a golden image pipeline with HCP Packer
- Learn how to automate your AMI builds using GitHub Actions.