Automated build system for immutable, zero-identity AMIs using Packer and Ansible with CI/CD promotion pipeline. Supports multiple Ubuntu versions via variable configuration.
Builds Ubuntu AMIs (22.04, 24.04, 20.04) with pre-installed enterprise software:
- Docker and containerd
- Kubernetes tools (kubectl, kubeadm, kubelet, Helm)
- Fluent Bit for log aggregation
- Prometheus Node Exporter for metrics
- HashiCorp Vault CLI for secrets management
- AppArmor, Fail2ban, Auditd for security
- SSSD/Realmd for Active Directory integration
- Certbot for HTTPS certificate management
All machine-specific identifiers are removed before AMI creation (zero-identity).
Build Process:
- Packer provisions EC2 instance from base Ubuntu AMI
- Ansible runs provisioning roles
- Zero-identity cleanup removes all machine-specific data
- AMI snapshot created
Deployment Pipeline:
- DEV: Automatic build on push to
developbranch - STAGING: Automatic promotion on push to
mainbranch - PRODUCTION: Manual workflow dispatch with approval gate
Rollback:
- GitHub Actions workflow or CLI script
- Automated launch template updates
- ASG instance refresh
- AWS Account with appropriate IAM permissions
- Packer >= 1.10.0
- Ansible >= 2.15.0
- AWS CLI configured
- GitHub Secrets configured
Ubuntu 22.04 build:
cd packer
packer init golden-image.pkr.hcl
packer build \
-var "os_version=22.04" \
-var "os_codename=jammy" \
-var "environment=dev" \
golden-image.pkr.hclUbuntu 24.04 build:
packer build \
-var "os_version=24.04" \
-var "os_codename=noble" \
-var "environment=dev" \
golden-image.pkr.hclUsing environment variable files:
packer build -var-file="../config/environments/dev.auto.pkrvars.hcl" golden-image.pkr.hclDeploy and rollback:
./scripts/deploy-ami.sh dev
./scripts/rollback-ami.sh devValidate configuration:
./scripts/validate-ansible.shAll software versions defined in: ansible/roles/k8s_prereqs/vars/main.yml
docker_version: "5:24.0.7-1~ubuntu.22.04~jammy"
kubernetes_version: "1.28.4"
helm_version: "3.13.2"
node_exporter_version: "1.7.0"
vault_version: "1.15.4-1"Update versions by editing this file.
os_version- Ubuntu version (22.04, 24.04, 20.04)os_codename- Ubuntu codename (jammy, noble, focal)environment- Environment tag (dev, staging, production)image_version- Semantic versionaws_region- AWS regionencrypt_boot- EBS encryption
DEV:
- Trigger: Push to
developbranch - Build: Automatic
- Test: Automated instance validation
- Approval: None
STAGING:
- Trigger: Push to
mainbranch - Build: Automatic after DEV tests pass
- Approval: None
PRODUCTION:
- Trigger: Manual workflow dispatch
- Build: Requires manual approval in GitHub UI
- Approval: Required
DEV:
AWS_ROLE_ARN_DEVVPC_ID_DEVSUBNET_ID_DEVSG_ID_DEVINSTANCE_PROFILE_DEV
STAGING:
AWS_ROLE_ARN_STAGING
PRODUCTION:
AWS_ROLE_ARN_PRODKMS_KEY_ID_PROD
Optional:
SHARE_AMI_ACCOUNTS- AWS account IDs for AMI sharingSLACK_WEBHOOK_URL- Notification webhook
DEV build:
git checkout develop
git commit -am "Update configuration"
git push origin developSTAGING build:
git checkout main
git merge develop
git push origin mainPRODUCTION build:
- Navigate to GitHub Actions tab
- Select "Golden Image CI/CD Pipeline"
- Click "Run workflow"
- Select environment:
production, version:1.0.0 - Approve deployment in GitHub UI
Removed items before AMI creation:
- SSH host keys
- Machine-ID
- Cloud-init instance data
- User command histories
- SSH authorized keys
- DHCP leases
- Log files
- Netplan configuration
- Temporary files
- Package cache
Verification after instance launch:
cat /etc/machine-id # Should be unique
ls -la /etc/ssh/ssh_host_* # Fresh keys
cat ~/.bash_history # EmptyBase System:
- Ubuntu (22.04, 24.04, 20.04)
- Kernel tuning (BBR, optimized TCP)
- Common utilities
Container Runtime:
- Docker with containerd
- Docker Compose
Kubernetes:
- kubectl, kubeadm, kubelet
- Helm 3
Security:
- HashiCorp Vault CLI
- AppArmor (enforcing)
- Fail2ban
- Auditd
- SSH hardening
- Automated security updates
Observability:
- Prometheus Node Exporter (port 9100)
- Fluent Bit
Authentication:
- SSSD/Realmd
- Kerberos
- Helper scripts:
/usr/local/bin/ad-join.sh
HTTPS:
- Certbot
-
Create new Ansible role:
mkdir -p ansible/roles/my_app/tasks touch ansible/roles/my_app/tasks/main.yml
-
Define tasks:
--- - name: Install my application ansible.builtin.apt: name: my-app state: present become: yes
-
Add to playbook:
roles: - role: my_app tags: ['custom']
Edit ansible/roles/k8s_prereqs/vars/main.yml:
# Update this single file
docker_version: "5:24.0.8-1~ubuntu.22.04~jammy" # New version
kubernetes_version: "1.29.0" # New versionAll roles automatically use updated versions.
Use Packer variables:
packer build \
-var "environment=prod" \
-var "vpc_id=vpc-prod123" \
-var "subnet_id=subnet-prod456" \
ubuntu-2204.pkr.hclThe pipeline automatically:
- Launches test instance from new AMI
- Validates Docker installation
- Validates Kubernetes tools
- Verifies zero-identity state
- Terminates test instance
- Tags AMI with test results
# Launch instance from AMI
aws ec2 run-instances \
--image-id ami-xxxxx \
--instance-type t3.micro \
--key-name my-key \
--subnet-id subnet-xxxxx
# SSH into instance
ssh ubuntu@<instance-ip>
# Verify installations
docker --version
kubectl version --client
helm version
fluent-bit --version
certbot --version
# Check zero-identity
cat /etc/machine-id
ls /etc/ssh/ssh_host_*
history- Push to
developtriggers DEV build - Automated tests run
- Merge to
maintriggers STAGING build - Manual workflow dispatch for PRODUCTION
- Approval gate before deployment
- DEV: Auto-incremented
1.0.${BUILD_NUMBER} - STAGING: Promoted from DEV with same version
- PRODUCTION: Manual semantic versioning (e.g.,
2.1.0)
Using rollback script:
./scripts/rollback-ami.sh production
./scripts/rollback-ami.sh staging ami-specific-versionOr via GitHub Actions workflow dispatch.
- All packages updated to latest versions
- No default passwords or credentials
- SSH keys regenerated per instance
- IMDSv2 enforced
- EBS volumes encrypted
- Security group restrictions applied
- Use instance profiles (IAM roles) instead of access keys
- Enable VPC Flow Logs
- Use Systems Manager Session Manager instead of SSH
- Enable CloudWatch Logs for audit trails
- Implement least-privilege IAM policies
- Zero-Identity: Meets NIST compliance for image reuse
- Encryption: Complies with encryption at rest requirements
- Audit: Full build history in GitHub Actions logs
- Traceability: Git commit SHA tagged on each AMI
Default inputs:
- Systemd: All system journal logs
- Syslog:
/var/log/syslog - Auth:
/var/log/auth.log
Default output:
- Stdout: JSON lines (customize for production)
Update /etc/fluent-bit/fluent-bit.conf:
[OUTPUT]
Name es
Match *
Host elasticsearch.example.com
Port 9200
Index golden-image-logs
Type _docOr send to CloudWatch:
[OUTPUT]
Name cloudwatch_logs
Match *
region us-east-1
log_group_name /aws/ec2/golden-images
log_stream_prefix from-fluent-bit-
auto_create_group true# Enable debug logging
export PACKER_LOG=1
packer build ubuntu-2204.pkr.hcl# Run with verbose output
ansible-playbook playbook.yml -vvvCheck cloud-init logs in EC2 console:
# In instance
sudo cat /var/log/cloud-init-output.logVerify cloud-init is enabled:
sudo systemctl status cloud-initMonthly:
- Update base OS packages
- Review and update software versions in
vars/main.yml - Rebuild DEV image for testing
Quarterly:
- Update Kubernetes versions
- Review and update security configurations
- Audit AMI sharing permissions
Annually:
- Major version upgrades (if applicable)
- Security audit of build pipeline
- Review and update documentation
# List old AMIs
aws ec2 describe-images \
--owners self \
--filters "Name=tag:Environment,Values=production" \
--query 'Images | sort_by(@, &CreationDate)[:-3].[ImageId,CreationDate]' \
--output table
# Deregister (after confirming no instances use it)
aws ec2 deregister-image --image-id ami-xxxxx
# Delete snapshots
aws ec2 delete-snapshot --snapshot-id snap-xxxxx- Terraform Modules - Infrastructure as Code
- Kubernetes Configs - K8s deployments
- Monitoring Stack - Observability
This project is licensed under the MIT License - see LICENSE file for details.