AWS EC2 (Linux)

AWS EC2 (Linux)

By coder

Provision AWS EC2 VMs as Coder workspaces

Provision AWS EC2 VMs as Coder workspaces with this example template.

Prerequisites

Authentication

By default, this template authenticates to AWS using the provider's default authentication methods.

The simplest way (without making changes to the template) is via environment variables (e.g. AWS_ACCESS_KEY_ID) or a credentials file. If you are running Coder on a VM, this file must be in /home/coder/aws/credentials.

To use another authentication method, edit the template.

Required permissions / policy

The following sample policy allows Coder to create EC2 instances and modify instances provisioned by Coder:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "ec2:GetDefaultCreditSpecification",
        "ec2:DescribeIamInstanceProfileAssociations",
        "ec2:DescribeTags",
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceTypes",
        "ec2:DescribeInstanceStatus",
        "ec2:CreateTags",
        "ec2:RunInstances",
        "ec2:DescribeInstanceCreditSpecifications",
        "ec2:DescribeImages",
        "ec2:ModifyDefaultCreditSpecification",
        "ec2:DescribeVolumes"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CoderResources",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstanceAttribute",
        "ec2:UnmonitorInstances",
        "ec2:TerminateInstances",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:DeleteTags",
        "ec2:MonitorInstances",
        "ec2:CreateTags",
        "ec2:RunInstances",
        "ec2:ModifyInstanceAttribute",
        "ec2:ModifyInstanceCreditSpecification"
      ],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Coder_Provisioned": "true"
        }
      }
    }
  ]
}

Architecture

This template provisions the following resources:

  • AWS Instance

Coder uses aws_ec2_instance_state to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this community example of an ephemeral AWS instance.

Note This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.

code-server

code-server is installed via the startup_script argument in the coder_agent resource block. The coder_app resource is defined to access code-server through the dashboard UI over localhost:13337.

1terraform {
2  required_providers {
3    coder = {
4      source = "coder/coder"
5    }
6    cloudinit = {
7      source = "hashicorp/cloudinit"
8    }
9    aws = {
10      source = "hashicorp/aws"
11    }
12  }
13}
14
15# Last updated 2023-03-14
16# aws ec2 describe-regions | jq -r '[.Regions[].RegionName] | sort'
17data "coder_parameter" "region" {
18  name         = "region"
19  display_name = "Region"
20  description  = "The region to deploy the workspace in."
21  default      = "us-east-1"
22  mutable      = false
23  option {
24    name  = "Asia Pacific (Tokyo)"
25    value = "ap-northeast-1"
26    icon  = "/emojis/1f1ef-1f1f5.png"
27  }
28  option {
29    name  = "Asia Pacific (Seoul)"
30    value = "ap-northeast-2"
31    icon  = "/emojis/1f1f0-1f1f7.png"
32  }
33  option {
34    name  = "Asia Pacific (Osaka)"
35    value = "ap-northeast-3"
36    icon  = "/emojis/1f1ef-1f1f5.png"
37  }
38  option {
39    name  = "Asia Pacific (Mumbai)"
40    value = "ap-south-1"
41    icon  = "/emojis/1f1ee-1f1f3.png"
42  }
43  option {
44    name  = "Asia Pacific (Singapore)"
45    value = "ap-southeast-1"
46    icon  = "/emojis/1f1f8-1f1ec.png"
47  }
48  option {
49    name  = "Asia Pacific (Sydney)"
50    value = "ap-southeast-2"
51    icon  = "/emojis/1f1e6-1f1fa.png"
52  }
53  option {
54    name  = "Canada (Central)"
55    value = "ca-central-1"
56    icon  = "/emojis/1f1e8-1f1e6.png"
57  }
58  option {
59    name  = "EU (Frankfurt)"
60    value = "eu-central-1"
61    icon  = "/emojis/1f1ea-1f1fa.png"
62  }
63  option {
64    name  = "EU (Stockholm)"
65    value = "eu-north-1"
66    icon  = "/emojis/1f1ea-1f1fa.png"
67  }
68  option {
69    name  = "EU (Ireland)"
70    value = "eu-west-1"
71    icon  = "/emojis/1f1ea-1f1fa.png"
72  }
73  option {
74    name  = "EU (London)"
75    value = "eu-west-2"
76    icon  = "/emojis/1f1ea-1f1fa.png"
77  }
78  option {
79    name  = "EU (Paris)"
80    value = "eu-west-3"
81    icon  = "/emojis/1f1ea-1f1fa.png"
82  }
83  option {
84    name  = "South America (São Paulo)"
85    value = "sa-east-1"
86    icon  = "/emojis/1f1e7-1f1f7.png"
87  }
88  option {
89    name  = "US East (N. Virginia)"
90    value = "us-east-1"
91    icon  = "/emojis/1f1fa-1f1f8.png"
92  }
93  option {
94    name  = "US East (Ohio)"
95    value = "us-east-2"
96    icon  = "/emojis/1f1fa-1f1f8.png"
97  }
98  option {
99    name  = "US West (N. California)"
100    value = "us-west-1"
101    icon  = "/emojis/1f1fa-1f1f8.png"
102  }
103  option {
104    name  = "US West (Oregon)"
105    value = "us-west-2"
106    icon  = "/emojis/1f1fa-1f1f8.png"
107  }
108}
109
110data "coder_parameter" "instance_type" {
111  name         = "instance_type"
112  display_name = "Instance type"
113  description  = "What instance type should your workspace use?"
114  default      = "t3.micro"
115  mutable      = false
116  option {
117    name  = "2 vCPU, 1 GiB RAM"
118    value = "t3.micro"
119  }
120  option {
121    name  = "2 vCPU, 2 GiB RAM"
122    value = "t3.small"
123  }
124  option {
125    name  = "2 vCPU, 4 GiB RAM"
126    value = "t3.medium"
127  }
128  option {
129    name  = "2 vCPU, 8 GiB RAM"
130    value = "t3.large"
131  }
132  option {
133    name  = "4 vCPU, 16 GiB RAM"
134    value = "t3.xlarge"
135  }
136  option {
137    name  = "8 vCPU, 32 GiB RAM"
138    value = "t3.2xlarge"
139  }
140}
141
142provider "aws" {
143  region = data.coder_parameter.region.value
144}
145
146data "coder_workspace" "me" {}
147data "coder_workspace_owner" "me" {}
148
149data "aws_ami" "ubuntu" {
150  most_recent = true
151  filter {
152    name   = "name"
153    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
154  }
155  filter {
156    name   = "virtualization-type"
157    values = ["hvm"]
158  }
159  owners = ["099720109477"] # Canonical
160}
161
162resource "coder_agent" "dev" {
163  count          = data.coder_workspace.me.start_count
164  arch           = "amd64"
165  auth           = "aws-instance-identity"
166  os             = "linux"
167  startup_script = <<-EOT
168    set -e
169
170    # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
171  EOT
172
173  metadata {
174    key          = "cpu"
175    display_name = "CPU Usage"
176    interval     = 5
177    timeout      = 5
178    script       = "coder stat cpu"
179  }
180  metadata {
181    key          = "memory"
182    display_name = "Memory Usage"
183    interval     = 5
184    timeout      = 5
185    script       = "coder stat mem"
186  }
187  metadata {
188    key          = "disk"
189    display_name = "Disk Usage"
190    interval     = 600 # every 10 minutes
191    timeout      = 30  # df can take a while on large filesystems
192    script       = "coder stat disk --path $HOME"
193  }
194}
195
196# See https://registry.coder.com/modules/coder/code-server
197module "code-server" {
198  count  = data.coder_workspace.me.start_count
199  source = "registry.coder.com/modules/code-server/coder"
200
201  # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
202  version = "~> 1.0"
203
204  agent_id = coder_agent.dev[0].id
205  order    = 1
206}
207
208# See https://registry.coder.com/modules/jetbrains-gateway
209module "jetbrains_gateway" {
210  count  = data.coder_workspace.me.start_count
211  source = "registry.coder.com/modules/jetbrains-gateway/coder"
212
213  # JetBrains IDEs to make available for the user to select
214  jetbrains_ides = ["IU", "PY", "WS", "PS", "RD", "CL", "GO", "RM"]
215  default        = "IU"
216
217  # Default folder to open when starting a JetBrains IDE
218  folder = "/home/coder"
219
220  # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
221  version = "~> 1.0"
222
223  agent_id   = coder_agent.dev[0].id
224  agent_name = "dev"
225  order      = 2
226}
227
228locals {
229  hostname   = lower(data.coder_workspace.me.name)
230  linux_user = "coder"
231}
232
233data "cloudinit_config" "user_data" {
234  gzip          = false
235  base64_encode = false
236
237  boundary = "//"
238
239  part {
240    filename     = "cloud-config.yaml"
241    content_type = "text/cloud-config"
242
243    content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
244      hostname   = local.hostname
245      linux_user = local.linux_user
246    })
247  }
248
249  part {
250    filename     = "userdata.sh"
251    content_type = "text/x-shellscript"
252
253    content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", {
254      linux_user = local.linux_user
255
256      init_script = try(coder_agent.dev[0].init_script, "")
257    })
258  }
259}
260
261resource "aws_instance" "dev" {
262  ami               = data.aws_ami.ubuntu.id
263  availability_zone = "${data.coder_parameter.region.value}a"
264  instance_type     = data.coder_parameter.instance_type.value
265
266  user_data = data.cloudinit_config.user_data.rendered
267  tags = {
268    Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
269    # Required if you are using our example policy, see template README
270    Coder_Provisioned = "true"
271  }
272  lifecycle {
273    ignore_changes = [ami]
274  }
275}
276
277resource "coder_metadata" "workspace_info" {
278  resource_id = aws_instance.dev.id
279  item {
280    key   = "region"
281    value = data.coder_parameter.region.value
282  }
283  item {
284    key   = "instance type"
285    value = aws_instance.dev.instance_type
286  }
287  item {
288    key   = "disk"
289    value = "${aws_instance.dev.root_block_device[0].volume_size} GiB"
290  }
291}
292
293resource "aws_ec2_instance_state" "dev" {
294  instance_id = aws_instance.dev.id
295  state       = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
296}
297