EC2 Instance
EC2 instance with IMDSv2, encrypted EBS, instance profile, and EIP — secure defaults out of the box.
Verification
Live-testedReally deployed, verified, idempotent and destroyed in a cloud sandbox.
Conformance
- Static validation (fmt · validate · tflint)
- Security scan: findings disclosed (Checkov)
- Plan tests (mocked: validation rules · outputs)
Provenance
- SHA-256 checksum
- Signature (pending)
Functional
- Live-tested — applied, verified, destroyed
Last verified 2026-06-19 · how we verify
Documentation
aws-ec2-instance — EC2 Instance
EC2 instance with IMDSv2, encrypted EBS, instance profile, and EIP — secure
defaults out of the box. IMDSv2 is enforced (http_tokens = required), the
root volume and any data volumes are always encrypted (gp3 by default), and the
module ships an SSM-agent-ready instance profile so you get Session Manager
shell access with zero SSH keys and zero inbound ports. AMI defaults to the
latest Amazon Linux 2023 for your architecture (Graviton arm64 by default).
Works with Terraform and OpenTofu (>= 1.6), AWS provider >= 6.0, < 7.0.
Usage
module "app_server" {
source = "./aws-ec2-instance"
name = "app-1"
instance_type = "t4g.small" # Graviton; set architecture = "x86_64" for Intel/AMD types
subnet_id = module.vpc.private_subnet_ids[0]
vpc_security_group_ids = [module.app_sg.security_group_id]
data_volumes = {
data = { device_name = "/dev/xvdf", size = 100 }
}
tags = { Environment = "prod" }
}
Then connect with aws ssm start-session --target <instance_id> — no bastion,
no port 22.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
name | string | — | Name for instance + derived resources (required) |
subnet_id | string | — | Target subnet (required) |
vpc_security_group_ids | list(string) | — | ≥ 1 security group (required) |
ami_id | string | null | Explicit AMI; null = latest AL2023 lookup |
architecture | string | "arm64" | AMI lookup arch (arm64/x86_64); must match instance type |
instance_type | string | "t4g.small" | Instance type |
associate_public_ip_address | bool | false | Ephemeral public IP at launch |
create_eip | bool | false | Stable Elastic IP + association |
private_ip | string | null | Static primary private IP |
source_dest_check | bool | true | Disable only for NAT/router instances |
key_name | string | null | SSH key pair (usually unnecessary with SSM) |
user_data | string | null | Cloud-init user data |
user_data_replace_on_change | bool | false | Recreate instance on user_data change |
create_instance_profile | bool | true | Create role + instance profile |
instance_profile_name | string | null | Existing profile when not creating one |
enable_ssm | bool | true | Attach AmazonSSMManagedInstanceCore |
additional_policy_arns | set(string) | [] | Extra policies for the module-managed role |
metadata_http_put_response_hop_limit | number | 1 | IMDSv2 hop limit (2 for containers) |
enable_instance_metadata_tags | bool | false | Expose tags via IMDS |
monitoring | bool | false | Detailed CloudWatch monitoring |
ebs_optimized | bool | true | EBS-optimized launch |
enable_termination_protection | bool | false | disable_api_termination for pet instances |
root_volume_size | number | 20 | Root volume GiB |
root_volume_type | string | "gp3" | gp3/gp2/io1/io2 |
root_volume_iops / root_volume_throughput | number | null | gp3/io* tuning |
kms_key_arn | string | null | CMK for EBS; null = AWS-managed key (encryption always on) |
data_volumes | map(object) | {} | Extra encrypted volumes: { device_name, size, type?, iops?, throughput?, kms_key_arn? } |
tags | map(string) | {} | Tags applied to all resources |
Outputs
| Name | Description |
|---|---|
instance_id, instance_arn, availability_zone | Instance identifiers |
private_ip, public_ip | Addresses (public_ip prefers the EIP) |
eip_allocation_id | EIP allocation (null if disabled) |
primary_network_interface_id | Primary ENI |
ami_id | Effective AMI used |
iam_role_name, iam_role_arn, instance_profile_name | IAM wiring |
data_volume_ids | Logical name => volume ID map |
Notes
- The AL2023 lookup pins nothing: each plan resolves the latest AMI, which
can replace the instance on a new release. Pin
ami_idfor fleets that must not drift. - Graviton (
arm64) is the default for price/performance; set botharchitecture = "x86_64"and an x86instance_typetogether.
Requirements
- Terraform or OpenTofu
>= 1.6 hashicorp/aws>= 6.0, < 7.0
Verification
Static-validated (fmt, validate, tflint). Live apply/destroy testing pending cloud sandbox availability — see catalog status.
License
Commercial — IaC Bazaar EULA. © IaC Bazaar. Original work (not derived from a third-party module).