├── .editorconfig ├── terraform ├── .gitignore ├── variables.tf ├── route53.tf ├── outputs.tf ├── main.tf ├── setup.sh ├── vpc.tf └── ec2.tf └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tf] 2 | indent_size = 2 -------------------------------------------------------------------------------- /terraform/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .terraform.lock.hcl 3 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "ssh_public_key_filepath" { 2 | type = string 3 | default = "~/.ssh/code_server_rsa.pub" 4 | } 5 | 6 | variable "domain_zone_id" { 7 | type = string 8 | default = "" 9 | } 10 | 11 | variable "domain_name" { 12 | type = string 13 | default = "" 14 | } 15 | -------------------------------------------------------------------------------- /terraform/route53.tf: -------------------------------------------------------------------------------- 1 | resource "aws_route53_record" "code_server_a_record" { 2 | # this resource is created only when all variables exist 3 | count = (var.domain_zone_id != "" && var.domain_name != "") ? 1 : 0 4 | 5 | zone_id = var.domain_zone_id 6 | name = var.domain_name 7 | type = "A" 8 | ttl = 300 9 | records = [aws_eip.code_server_public_ip.public_ip] 10 | } 11 | -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | # print the public IP of the instance 2 | output "instance_public_ip" { 3 | description = "Public IP address of the EC2 instance" 4 | value = aws_eip.code_server_public_ip.public_ip 5 | } 6 | 7 | # print the availability zone of the instance 8 | output "availability_zones" { 9 | description = "Availability zones of Subnet and EC2 instance" 10 | value = data.aws_availability_zones.available.names[0] 11 | } 12 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | # because the key and region are already set in the environment 2 | # there is no need to put anything here (it is also not recommended to set the key in the hardcode way) 3 | # there is no problem to delete this porvider block directly 4 | provider "aws" { 5 | region = "ap-northeast-2" 6 | # access_key = "my-access-key" 7 | # secret_key = "my-secret-key" 8 | } 9 | 10 | # in multi-person collaboration, terraform's best practice recommends using state lock and remote state 11 | # there is no problem to delete this block, only means that we are using local state here 12 | terraform { 13 | # ref: 14 | backend "s3" { 15 | bucket = "terraform-state-archives" 16 | key = "vs-code-server-terraform.tfstate" 17 | region = "ap-northeast-2" 18 | dynamodb_table = "vs-code-state-locking" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build VS Code Server with Terraform 2 | 3 | You can quickly build a [vscode server](https://code.visualstudio.com/blogs/2022/07/07/vscode-server) in the AWS with this terraform project 4 | 5 | ## Usage 6 | 7 | > **Note** 8 | > 9 | > You need to install terraform and configure AWS IAM 10 | > 11 | 12 | Init the terraform project 13 | 14 | ```bash 15 | terraform init 16 | ``` 17 | 18 | Check the terraform deploy plan 19 | 20 | ```bash 21 | terraform plan 22 | ``` 23 | 24 | Start to deploy the vscode server in the AWS cloud 25 | 26 | ```bash 27 | terraform apply -var="ssh_public_key_filepath=~/.ssh/code_server_rsa.pub" 28 | ``` 29 | 30 | If you have a hosted domain in Route53, you could deploy the server with domain 31 | 32 | ```bash 33 | terraform apply -var="ssh_public_key_filepath=~/.ssh/code_server_rsa.pub" -var="domain_zone_id=YOUR_DOMAIN_ZONE_ID" -var="domain_name=YOUR_PREFER_DOMAIN_NAME" 34 | ``` 35 | -------------------------------------------------------------------------------- /terraform/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit immediately if a command exits with a non-zero status 4 | set -e 5 | 6 | # user data will use root user, so do not add "sudo" 7 | apt update 8 | apt upgrade -y 9 | 10 | # install nginx 11 | apt install -y nginx 12 | systemctl enable nginx 13 | 14 | # set nginx setting 15 | touch /etc/nginx/sites-available/code-server 16 | echo "server { 17 | listen 80; 18 | listen [::]:80; 19 | server_name ~^.*\$; 20 | 21 | location / { 22 | proxy_pass http://localhost:8080/; 23 | proxy_set_header Host \$host; 24 | proxy_set_header Upgrade \$http_upgrade; 25 | proxy_set_header Connection upgrade; 26 | proxy_set_header Accept-Encoding gzip; 27 | } 28 | }" | tee -a /etc/nginx/sites-available/code-server 29 | ln -s /etc/nginx/sites-available/code-server /etc/nginx/sites-enabled/code-server 30 | systemctl restart nginx 31 | 32 | # ubuntu 22.04 EC2 dafault user is "ubuntu" 33 | runuser ubuntu -c "curl -fsSL https://code-server.dev/install.sh | sh" 34 | runuser ubuntu -c "sudo systemctl enable --now code-server@ubuntu" -------------------------------------------------------------------------------- /terraform/vpc.tf: -------------------------------------------------------------------------------- 1 | # set virtual private cloud (vpc) 2 | resource "aws_vpc" "code_server_vpc" { 3 | cidr_block = "10.0.0.0/16" 4 | enable_dns_hostnames = true 5 | enable_dns_support = true 6 | } 7 | 8 | # set up internet gateways to allow communication between the instance in the vpc and the external internet 9 | resource "aws_internet_gateway" "code_server_gateway" { 10 | vpc_id = aws_vpc.code_server_vpc.id 11 | } 12 | 13 | # set up subnet, the instance will place in this subnet 14 | resource "aws_subnet" "code_server_subnet" { 15 | vpc_id = aws_vpc.code_server_vpc.id 16 | # subnet's availability zone needs to be the same as instance's availability zone 17 | availability_zone = data.aws_availability_zones.available.names[0] 18 | cidr_block = "10.0.0.0/24" 19 | } 20 | 21 | # set up the route table, enables network packets to flow in and out efficiently 22 | resource "aws_route_table" "code_server_route_table" { 23 | vpc_id = aws_vpc.code_server_vpc.id 24 | 25 | route { 26 | cidr_block = "0.0.0.0/0" 27 | gateway_id = aws_internet_gateway.code_server_gateway.id 28 | } 29 | } 30 | 31 | resource "aws_route_table_association" "subnet-association" { 32 | subnet_id = aws_subnet.code_server_subnet.id 33 | route_table_id = aws_route_table.code_server_route_table.id 34 | } 35 | 36 | # set instance's public IP 37 | resource "aws_eip" "code_server_public_ip" { 38 | instance = aws_instance.code_server.id 39 | vpc = true 40 | } 41 | -------------------------------------------------------------------------------- /terraform/ec2.tf: -------------------------------------------------------------------------------- 1 | # instance type has a corresponding availability zone in each region 2 | # here the availability zones are filtered by the resource aws_availability_zones 3 | # ref: https://aws.amazon.com/tw/premiumsupport/knowledge-center/ec2-instance-type-not-supported-az-error/ 4 | data "aws_availability_zones" "available" { 5 | state = "available" 6 | } 7 | 8 | # filter option can refer to aws cli "describe-images" 9 | # ref: https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html 10 | data "aws_ami" "ubuntu" { 11 | most_recent = true 12 | 13 | filter { 14 | name = "architecture" 15 | values = ["arm64"] 16 | } 17 | 18 | filter { 19 | name = "name" 20 | values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-arm64-server-*"] 21 | } 22 | 23 | filter { 24 | name = "virtualization-type" 25 | values = ["hvm"] 26 | } 27 | 28 | owners = ["099720109477"] # Canonical 29 | } 30 | 31 | resource "aws_instance" "code_server" { 32 | ami = data.aws_ami.ubuntu.id 33 | instance_type = "t4g.small" 34 | # set ssh key pair of remote instance 35 | key_name = aws_key_pair.code_server_key_pair.key_name 36 | security_groups = [aws_security_group.code_server_security_group.id] 37 | availability_zone = data.aws_availability_zones.available.names[0] 38 | # when instance launched, excute the configuration tasks 39 | user_data_base64 = data.template_cloudinit_config.setup.rendered 40 | user_data_replace_on_change = true 41 | 42 | tags = { 43 | Name = "code_server" 44 | } 45 | 46 | # set elatic block storage (EBS) 47 | ebs_block_device { 48 | device_name = "/dev/sda1" 49 | volume_size = 20 50 | } 51 | 52 | # place the instance in the specified subnet 53 | subnet_id = aws_subnet.code_server_subnet.id 54 | } 55 | 56 | data "template_cloudinit_config" "setup" { 57 | gzip = true 58 | base64_encode = true 59 | 60 | part { 61 | filename = "setup.sh" 62 | content_type = "text/x-shellscript" 63 | content = file("setup.sh") 64 | } 65 | } 66 | 67 | # set security groups, similar to firewall 68 | resource "aws_security_group" "code_server_security_group" { 69 | name = "code_server" 70 | vpc_id = aws_vpc.code_server_vpc.id 71 | 72 | ingress { 73 | description = "SSH" 74 | from_port = 22 75 | to_port = 22 76 | protocol = "tcp" 77 | cidr_blocks = ["0.0.0.0/0"] 78 | } 79 | 80 | ingress { 81 | description = "HTTP" 82 | from_port = 80 83 | to_port = 80 84 | protocol = "tcp" 85 | cidr_blocks = ["0.0.0.0/0"] 86 | } 87 | 88 | ingress { 89 | description = "HTTPS" 90 | from_port = 443 91 | to_port = 443 92 | protocol = "tcp" 93 | cidr_blocks = ["0.0.0.0/0"] 94 | } 95 | 96 | egress { 97 | from_port = 0 98 | to_port = 0 99 | protocol = "-1" 100 | cidr_blocks = ["0.0.0.0/0"] 101 | } 102 | } 103 | 104 | # set ssh key pair to connect the instance 105 | resource "aws_key_pair" "code_server_key_pair" { 106 | key_name = "code_server" 107 | public_key = file(var.ssh_public_key_filepath) 108 | } 109 | --------------------------------------------------------------------------------