AWS EC2 (Linux)
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