In modern cloud infrastructure—especially in environments that span multiple classification domains—custom Amazon Machine Images (AMIs) become a foundation for secure, reproducible, and compliant server deployments.
Whether you’re building hardened Linux images, enforcing baseline security controls, or deploying across environments with varying security postures (e.g., IL2, IL4, IL6), scripting AMI creation and management ensures consistency and control.
What You’ll Learn
- How to automate AMI creation using EC2 Image Builder & CLI
- How to secure OS images with configuration scripts and IAM policies
- How to promote images across domains (e.g., dev → staging → production)
- How to integrate classification tagging, lifecycle controls, and encrypted snapshots
1. Automating Custom AMI Creation
Option A: Using aws ec2 create-image
Launch and configure your EC2 instance manually or via script:
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.medium \
--key-name secure-key \
--security-group-ids sg-01234 \
--subnet-id subnet-01234
Once configured (e.g., install agents, update packages), create an AMI:
aws ec2 create-image \
--instance-id i-0123456789abcdef0 \
--name "hardened-linux-base-2025-05-09" \
--no-reboot
✅ Add
"--no-reboot"
only if you understand the risks—some in-memory config may be lost.
Option B: Using EC2 Image Builder for Codified AMI Pipelines
Define components in YAML and automate builds:
name: HardenedImagePipeline
version: 1.0.0
components:
- name: baseline-linux-hardening
description: "Applies STIG rules and disables root SSH"
phases:
- name: build
steps:
- action: ExecuteBash
inputs:
commands:
- apt-get update && apt-get upgrade -y
- systemctl disable ssh
- useradd secureops && mkdir /home/secureops
Register and execute using the AWS CLI:
aws imagebuilder create-image-pipeline \
--name HardenedPipeline \
--infrastructure-configuration arn:aws:imagebuilder:... \
--image-recipe arn:aws:imagebuilder:...
🛠️ You can trigger this pipeline from Git commits, CloudWatch events, or weekly schedules.
2. Hardening Images with Scripts
Inject security via provisioning scripts.
Example: Harden Ubuntu with a bootstrap script
#!/bin/bash
# Disable root login
passwd -l root
# Disable SSH password auth
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
# Install audit tools
apt install -y auditd aide fail2ban
# Remove unused packages
apt autoremove -y
Pass this script using --user-data
in EC2 or Image Builder.
3. Tagging and Classifying AMIs for Domain Control
Use tags to track image purpose, domain, classification level, and approval state.
aws ec2 create-tags \
--resources ami-0abc1234567890 \
--tags Key=Domain,Value=IL4 Key=Classification,Value=SECRET Key=Approved,Value=True
Enforce access using IAM policies:
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "*",
"Condition": {
"StringEquals": {
"ec2:ResourceTag/Classification": "TOP-SECRET"
}
}
}
4. Promoting Images Across Classification Tiers
Use AMI copy and encryption to transfer across domains while enforcing controls.
Copy AMI to another region or enclave:
aws ec2 copy-image \
--source-image-id ami-0abc1234567890 \
--source-region us-west-2 \
--region us-gov-west-1 \
--name "secure-app-prod"
Encrypt with KMS key per domain:
aws ec2 copy-image \
--source-image-id ami-0abc1234567890 \
--kms-key-id arn:aws:kms:gov-west-1:1234567890:key/xyz \
--encrypted
✅ Encrypted AMIs are required for classified or enclave-compliant workloads.
5. Image Cleanup and Lifecycle Automation
Create lifecycle scripts or Lambda functions to:
- Deregister old AMIs
- Delete orphaned snapshots
- Enforce image rotation (e.g., retain last 5)
Python: Clean up AMIs older than 60 days
import boto3
from datetime import datetime, timedelta
ec2 = boto3.client('ec2')
cutoff = datetime.utcnow() - timedelta(days=60)
images = ec2.describe_images(Owners=['self'])['Images']
for image in images:
created = datetime.strptime(image['CreationDate'], "%Y-%m-%dT%H:%M:%S.%fZ")
if created < cutoff:
ec2.deregister_image(ImageId=image['ImageId'])
Bonus: Versioned Infrastructure with Packer
Use HashiCorp Packer to version and script image creation across providers.
{
"builders": [{
"type": "amazon-ebs",
"ami_name": "secure-linux-{{timestamp}}",
"source_ami": "ami-0abc1234567890",
"instance_type": "t3.micro",
"ssh_username": "ubuntu",
"region": "us-east-1"
}],
"provisioners": [{
"type": "shell",
"script": "scripts/harden.sh"
}]
}
Run:
packer build secure-linux.json
Summary Table
Task | Tool | Example |
---|---|---|
Create AMI | AWS CLI | aws ec2 create-image |
Automate Build | EC2 Image Builder | YAML pipelines |
Harden System | Bash scripts | Disable root, install audit tools |
Tag/Track | create-tags | Classification labels |
Promote | copy-image + encryption | Region to region/domain |
Cleanup | Python + Boto3 | Deregister old AMIs |
Final Thoughts
Creating secure, reproducible server images is essential for any multi-domain or compliance-sensitive deployment. But doing it by hand isn’t scalable. By embracing scripting and infrastructure-as-code, you ensure your images remain consistent, hardened, and ready for production across environments—without sacrificing control or visibility.