Skip to content

feat(examples/templates): add aws vm devcontainer template #11248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions examples/templates/aws-devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
display_name: AWS EC2 (Devcontainer)
description: Provision AWS EC2 VMs with a devcontainer as Coder workspaces
icon: ../../../site/static/icon/aws.png
maintainer_github: coder
verified: true
tags: [vm, linux, aws, persistent, devcontainer]
---

# Remote Development on AWS EC2 VMs using a Devcontainer

Provision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs/v2/latest) with this example template.
![Architecture Diagram](./architecture.svg)

<!-- TODO: Add screenshot -->

## Prerequisites

### Authentication

By default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).

The simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.

To use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.

## Required permissions / policy

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

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:GetDefaultCreditSpecification",
"ec2:DescribeIamInstanceProfileAssociations",
"ec2:DescribeTags",
"ec2:DescribeInstances",
"ec2:DescribeInstanceTypes",
"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](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) 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 [`code-server`](https://registry.coder.com/modules/code-server) registry module. For a list of all modules and templates pplease check [Coder Registry](https://registry.coder.com).
8 changes: 8 additions & 0 deletions examples/templates/aws-devcontainer/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
181 changes: 181 additions & 0 deletions examples/templates/aws-devcontainer/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
aws = {
source = "hashicorp/aws"
}
}
}

module "aws_region" {
source = "https://registry.coder.com/modules/aws-region"
default = "us-east-1"
}

data "coder_parameter" "instance_type" {
name = "instance_type"
display_name = "Instance type"
description = "What instance type should your workspace use?"
default = "t3.micro"
mutable = false
option {
name = "2 vCPU, 1 GiB RAM"
value = "t3.micro"
}
option {
name = "2 vCPU, 2 GiB RAM"
value = "t3.small"
}
option {
name = "2 vCPU, 4 GiB RAM"
value = "t3.medium"
}
option {
name = "2 vCPU, 8 GiB RAM"
value = "t3.large"
}
option {
name = "4 vCPU, 16 GiB RAM"
value = "t3.xlarge"
}
option {
name = "8 vCPU, 32 GiB RAM"
value = "t3.2xlarge"
}
}

provider "aws" {
region = module.aws_region.value
}

data "coder_workspace" "me" {
}

data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}

data "coder_parameter" "repo_url" {
name = "repo_url"
display_name = "Repository URL"
default = "https://github.com/coder/envbuilder-starter-devcontainer"
description = "Repository URL"
mutable = true
}

resource "coder_agent" "dev" {
count = data.coder_workspace.me.start_count
arch = "amd64"
auth = "token"
os = "linux"
dir = "/workspaces/${trimsuffix(basename(data.coder_parameter.repo_url.value), ".git")}"
connection_timeout = 0

metadata {
key = "cpu"
display_name = "CPU Usage"
interval = 5
timeout = 5
script = "coder stat cpu"
}
metadata {
key = "memory"
display_name = "Memory Usage"
interval = 5
timeout = 5
script = "coder stat mem"
}
}

module "code-server" {
count = data.coder_workspace.me.start_count
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.dev[0].id
}

locals {
linux_user = "coder"
user_data = <<-EOT
Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]
hostname: ${lower(data.coder_workspace.me.name)}
users:
- name: ${local.linux_user}
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
# Install Docker
if ! command -v docker &> /dev/null
then
echo "Docker not found, installing..."
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh 2>&1 >/dev/null
usermod -aG docker ${local.linux_user}
newgrp docker
else
echo "Docker is already installed."
fi

# Start envbuilder
docker run --rm \
-v /home/${local.linux_user}/envbuilder:/workspaces \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this persists now? nice!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's persistent. I have tested.

-e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \
-e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \
-e GIT_URL="${data.coder_parameter.repo_url.value}" \
-e INIT_SCRIPT="echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh" \
-e FALLBACK_IMAGE="codercom/enterprise-base:ubuntu" \
ghcr.io/coder/envbuilder
--//--
EOT
}

resource "aws_instance" "vm" {
ami = data.aws_ami.ubuntu.id
availability_zone = "${module.aws_region.value}a"
instance_type = data.coder_parameter.instance_type.value
root_block_device {
volume_size = 30
}

user_data = local.user_data
tags = {
Name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}"
# Required if you are using our example policy, see template README
Coder_Provisioned = "true"
}
lifecycle {
ignore_changes = [ami]
}
}

resource "aws_ec2_instance_state" "vm" {
instance_id = aws_instance.vm.id
state = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
}