├── .gitignore ├── README.md ├── app-variables.tf ├── key-pair-main.tf ├── network-main.tf ├── network-variables.tf ├── provider-main.tf ├── provider-variables.tf ├── terraform.tfvars ├── windows-versions.tf ├── windows-vm-main.tf ├── windows-vm-output.tf └── windows-vm-variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | # example.tfvars 16 | 17 | # Ignore override files as they are usually used to override resources locally and so 18 | # are not checked in 19 | override.tf 20 | override.tf.json 21 | *_override.tf 22 | *_override.tf.json 23 | 24 | # Include override files you do wish to add to version control using negated pattern 25 | # 26 | # !example_override.tf 27 | 28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 29 | # example: *tfplan* 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform AWS Windows Server EC2 Instance 2 | 3 | Deploying a Windows Server EC2 Instance in AWS using Terraform 4 | 5 | To update the version of Windows Server, just update the ami line in the **windows-vm-main.tf** file, with a variable from the **windows-versions.tf** file. 6 | 7 | In this file, we support latest versions of: 8 | 9 | - Windows Server 2022 10 | - Windows Server 2019 11 | - Windows Server 2016 12 | - Windows Server 2012 R2 13 | -------------------------------------------------------------------------------- /app-variables.tf: -------------------------------------------------------------------------------- 1 | #################################### 2 | ## Application Module - Variables ## 3 | #################################### 4 | 5 | # Application definition 6 | 7 | variable "app_name" { 8 | type = string 9 | description = "Application name" 10 | } 11 | 12 | variable "app_environment" { 13 | type = string 14 | description = "Application environment" 15 | } 16 | -------------------------------------------------------------------------------- /key-pair-main.tf: -------------------------------------------------------------------------------- 1 | ##################### 2 | ## Key Pair - Main ## 3 | ##################### 4 | 5 | # Generates a secure private key and encodes it as PEM 6 | resource "tls_private_key" "key_pair" { 7 | algorithm = "RSA" 8 | rsa_bits = 4096 9 | } 10 | 11 | # Create the Key Pair 12 | resource "aws_key_pair" "key_pair" { 13 | key_name = "${lower(var.app_name)}-${lower(var.app_environment)}-windows-${lower(var.aws_region)}" 14 | public_key = tls_private_key.key_pair.public_key_openssh 15 | } 16 | 17 | # Save file 18 | resource "local_file" "ssh_key" { 19 | filename = "${aws_key_pair.key_pair.key_name}.pem" 20 | content = tls_private_key.key_pair.private_key_pem 21 | } 22 | -------------------------------------------------------------------------------- /network-main.tf: -------------------------------------------------------------------------------- 1 | ########################################## 2 | ## Network Single AZ Public Only - Main ## 3 | ########################################## 4 | 5 | # Create the VPC 6 | resource "aws_vpc" "vpc" { 7 | cidr_block = var.vpc_cidr 8 | enable_dns_hostnames = true 9 | tags = { 10 | Name = "${lower(var.app_name)}-${lower(var.app_environment)}-vpc" 11 | Environment = var.app_environment 12 | } 13 | } 14 | 15 | # Define the public subnet 16 | resource "aws_subnet" "public-subnet" { 17 | vpc_id = aws_vpc.vpc.id 18 | cidr_block = var.public_subnet_cidr 19 | availability_zone = var.aws_az 20 | tags = { 21 | Name = "${lower(var.app_name)}-${lower(var.app_environment)}-public-subnet" 22 | Environment = var.app_environment 23 | } 24 | } 25 | 26 | # Define the internet gateway 27 | resource "aws_internet_gateway" "gw" { 28 | vpc_id = aws_vpc.vpc.id 29 | tags = { 30 | Name = "${lower(var.app_name)}-${lower(var.app_environment)}-igw" 31 | Environment = var.app_environment 32 | } 33 | } 34 | 35 | # Define the public route table 36 | resource "aws_route_table" "public-rt" { 37 | vpc_id = aws_vpc.vpc.id 38 | route { 39 | cidr_block = "0.0.0.0/0" 40 | gateway_id = aws_internet_gateway.gw.id 41 | } 42 | tags = { 43 | Name = "${lower(var.app_name)}-${lower(var.app_environment)}-public-subnet-rt" 44 | Environment = var.app_environment 45 | } 46 | } 47 | 48 | # Assign the public route table to the public subnet 49 | resource "aws_route_table_association" "public-rt-association" { 50 | subnet_id = aws_subnet.public-subnet.id 51 | route_table_id = aws_route_table.public-rt.id 52 | } 53 | -------------------------------------------------------------------------------- /network-variables.tf: -------------------------------------------------------------------------------- 1 | ############################################## 2 | ## Network Single AZ Public Only - Variables # 3 | ############################################## 4 | 5 | # AWS AZ 6 | variable "aws_az" { 7 | type = string 8 | description = "AWS AZ" 9 | default = "eu-west-1c" 10 | } 11 | 12 | # VPC Variables 13 | variable "vpc_cidr" { 14 | type = string 15 | description = "CIDR for the VPC" 16 | default = "10.1.64.0/18" 17 | } 18 | 19 | # Subnet Variables 20 | variable "public_subnet_cidr" { 21 | type = string 22 | description = "CIDR for the public subnet" 23 | default = "10.1.64.0/24" 24 | } -------------------------------------------------------------------------------- /provider-main.tf: -------------------------------------------------------------------------------- 1 | ################################ 2 | ## AWS Provider Module - Main ## 3 | ################################ 4 | 5 | # AWS Provider 6 | terraform { 7 | required_providers { 8 | aws = { 9 | source = "hashicorp/aws" 10 | version = "~> 3.0" 11 | } 12 | } 13 | } 14 | 15 | provider "aws" { 16 | access_key = var.aws_access_key 17 | secret_key = var.aws_secret_key 18 | region = var.aws_region 19 | } 20 | 21 | -------------------------------------------------------------------------------- /provider-variables.tf: -------------------------------------------------------------------------------- 1 | ##################################### 2 | ## AWS Provider Module - Variables ## 3 | ##################################### 4 | 5 | # AWS connection & authentication 6 | 7 | variable "aws_access_key" { 8 | type = string 9 | description = "AWS access key" 10 | } 11 | 12 | variable "aws_secret_key" { 13 | type = string 14 | description = "AWS secret key" 15 | } 16 | 17 | variable "aws_region" { 18 | type = string 19 | description = "AWS region" 20 | } 21 | 22 | -------------------------------------------------------------------------------- /terraform.tfvars: -------------------------------------------------------------------------------- 1 | # Application Definition 2 | app_name = "kopicloud" # Do NOT enter any spaces 3 | app_environment = "dev" # Dev, Test, Staging, Prod, etc 4 | 5 | # Network 6 | vpc_cidr = "10.11.0.0/16" 7 | public_subnet_cidr = "10.11.1.0/24" 8 | 9 | # AWS Settings 10 | aws_access_key = "complete-this" 11 | aws_secret_key = "complete-this" 12 | aws_region = "eu-west-1" 13 | 14 | # Windows Virtual Machine 15 | windows_instance_name = "kopiwinsrv01" 16 | windows_instance_type = "t2.micro" 17 | windows_associate_public_ip_address = true 18 | windows_root_volume_size = 30 19 | windows_root_volume_type = "gp2" 20 | windows_data_volume_size = 10 21 | windows_data_volume_type = "gp2" 22 | -------------------------------------------------------------------------------- /windows-versions.tf: -------------------------------------------------------------------------------- 1 | ################################################ 2 | # Get latest Windows Server AMI with Terraform # 3 | ################################################ 4 | 5 | # Get latest Windows Server 2012R2 AMI 6 | data "aws_ami" "windows-2012-r2" { 7 | most_recent = true 8 | owners = ["amazon"] 9 | filter { 10 | name = "name" 11 | values = ["Windows_Server-2012-R2_RTM-English-64Bit-Base-*"] 12 | } 13 | } 14 | 15 | # Get latest Windows Server 2016 AMI 16 | data "aws_ami" "windows-2016" { 17 | most_recent = true 18 | owners = ["amazon"] 19 | filter { 20 | name = "name" 21 | values = ["Windows_Server-2016-English-Full-Base*"] 22 | } 23 | } 24 | 25 | # Get latest Windows Server 2019 AMI 26 | data "aws_ami" "windows-2019" { 27 | most_recent = true 28 | owners = ["amazon"] 29 | filter { 30 | name = "name" 31 | values = ["Windows_Server-2019-English-Full-Base*"] 32 | } 33 | } 34 | 35 | # Get latest Windows Server 2022 AMI 36 | data "aws_ami" "windows-2022" { 37 | most_recent = true 38 | owners = ["amazon"] 39 | filter { 40 | name = "name" 41 | values = ["Windows_Server-2022-English-Full-Base*"] 42 | } 43 | } -------------------------------------------------------------------------------- /windows-vm-main.tf: -------------------------------------------------------------------------------- 1 | ################################### 2 | ## Virtual Machine Module - Main ## 3 | ################################### 4 | 5 | # Bootstrapping PowerShell Script 6 | data "template_file" "windows-userdata" { 7 | template = < 9 | # Rename Machine 10 | Rename-Computer -NewName "${var.windows_instance_name}" -Force; 11 | 12 | # Install IIS 13 | Install-WindowsFeature -name Web-Server -IncludeManagementTools; 14 | 15 | # Restart machine 16 | shutdown -r -t 10; 17 | 18 | EOF 19 | } 20 | 21 | # Create EC2 Instance 22 | resource "aws_instance" "windows-server" { 23 | ami = data.aws_ami.windows-2019.id 24 | instance_type = var.windows_instance_type 25 | subnet_id = aws_subnet.public-subnet.id 26 | vpc_security_group_ids = [aws_security_group.aws-windows-sg.id] 27 | associate_public_ip_address = var.windows_associate_public_ip_address 28 | source_dest_check = false 29 | key_name = aws_key_pair.key_pair.key_name 30 | user_data = data.template_file.windows-userdata.rendered 31 | 32 | # root disk 33 | root_block_device { 34 | volume_size = var.windows_root_volume_size 35 | volume_type = var.windows_root_volume_type 36 | delete_on_termination = true 37 | encrypted = true 38 | } 39 | 40 | # extra disk 41 | ebs_block_device { 42 | device_name = "/dev/xvda" 43 | volume_size = var.windows_data_volume_size 44 | volume_type = var.windows_data_volume_type 45 | encrypted = true 46 | delete_on_termination = true 47 | } 48 | 49 | tags = { 50 | Name = "${lower(var.app_name)}-${var.app_environment}-windows-server" 51 | Environment = var.app_environment 52 | } 53 | } 54 | 55 | # Create Elastic IP for the EC2 instance 56 | resource "aws_eip" "windows-eip" { 57 | vpc = true 58 | tags = { 59 | Name = "${lower(var.app_name)}-${var.app_environment}-windows-eip" 60 | Environment = var.app_environment 61 | } 62 | } 63 | 64 | # Associate Elastic IP to Windows Server 65 | resource "aws_eip_association" "windows-eip-association" { 66 | instance_id = aws_instance.windows-server.id 67 | allocation_id = aws_eip.windows-eip.id 68 | } 69 | 70 | # Define the security group for the Windows server 71 | resource "aws_security_group" "aws-windows-sg" { 72 | name = "${lower(var.app_name)}-${var.app_environment}-windows-sg" 73 | description = "Allow incoming connections" 74 | vpc_id = aws_vpc.vpc.id 75 | 76 | ingress { 77 | from_port = 80 78 | to_port = 80 79 | protocol = "tcp" 80 | cidr_blocks = ["0.0.0.0/0"] 81 | description = "Allow incoming HTTP connections" 82 | } 83 | 84 | ingress { 85 | from_port = 3389 86 | to_port = 3389 87 | protocol = "tcp" 88 | cidr_blocks = ["0.0.0.0/0"] 89 | description = "Allow incoming RDP connections" 90 | } 91 | 92 | egress { 93 | from_port = 0 94 | to_port = 0 95 | protocol = "-1" 96 | cidr_blocks = ["0.0.0.0/0"] 97 | } 98 | 99 | tags = { 100 | Name = "${lower(var.app_name)}-${var.app_environment}-windows-sg" 101 | Environment = var.app_environment 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /windows-vm-output.tf: -------------------------------------------------------------------------------- 1 | ##################################### 2 | ## Virtual Machine Module - Output ## 3 | ##################################### 4 | 5 | output "vm_windows_server_instance_name" { 6 | value = var.windows_instance_name 7 | } 8 | 9 | output "vm_windows_server_instance_id" { 10 | value = aws_instance.windows-server.id 11 | } 12 | 13 | output "vm_windows_server_instance_public_dns" { 14 | value = aws_instance.windows-server.public_dns 15 | } 16 | 17 | output "vm_windows_server_instance_public_ip" { 18 | value = aws_eip.windows-eip.public_ip 19 | } 20 | 21 | output "vm_windows_server_instance_private_ip" { 22 | value = aws_instance.windows-server.private_ip 23 | } 24 | -------------------------------------------------------------------------------- /windows-vm-variables.tf: -------------------------------------------------------------------------------- 1 | ######################################## 2 | ## Virtual Machine Module - Variables ## 3 | ######################################## 4 | 5 | variable "windows_instance_type" { 6 | type = string 7 | description = "EC2 instance type for Windows Server" 8 | default = "t2.micro" 9 | } 10 | 11 | variable "windows_associate_public_ip_address" { 12 | type = bool 13 | description = "Associate a public IP address to the EC2 instance" 14 | default = true 15 | } 16 | 17 | variable "windows_root_volume_size" { 18 | type = number 19 | description = "Volumen size of root volumen of Windows Server" 20 | default = "30" 21 | } 22 | 23 | variable "windows_data_volume_size" { 24 | type = number 25 | description = "Volumen size of data volumen of Windows Server" 26 | default = "10" 27 | } 28 | 29 | variable "windows_root_volume_type" { 30 | type = string 31 | description = "Volumen type of root volumen of Windows Server. Can be standard, gp3, gp2, io1, sc1 or st1" 32 | default = "gp2" 33 | } 34 | 35 | variable "windows_data_volume_type" { 36 | type = string 37 | description = "Volumen type of data volumen of Windows Server. Can be standard, gp3, gp2, io1, sc1 or st1" 38 | default = "gp2" 39 | } 40 | 41 | variable "windows_instance_name" { 42 | type = string 43 | description = "EC2 instance name for Windows Server" 44 | default = "tfwinsrv01" 45 | } --------------------------------------------------------------------------------