mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add base templates for all major cloud providers (#26634)
Add 6 new base templates to the template builder, covering all major cloud providers and platforms: - **scratch**: Minimal starter template with only `coder_agent` and metadata - **aws-windows**: AWS EC2 Windows instances with PowerShell user_data - **azure-linux**: Azure VMs with cloud-init and managed disk persistence - **gcp-linux**: Google Compute Engine Linux instances with persistent disk - **gcp-windows**: Google Compute Engine Windows instances - **digitalocean-linux**: DigitalOcean Linux droplets with persistent volumes Each base includes `base.json`, `main.tf.tmpl`, `README.md` (with prerequisite markers), and any static files (cloud-init configs). Tests verify all 9 bases load, render without error, and produce valid single-agent declarations. `azure-windows` is deferred; it needs to be registered in the `examples` package first. Depends on #26633 > [!NOTE] > This PR was authored by Coder Agents on behalf of @jeremyruppel. --- NB: This is very much an agent-generated PR and draws completely from base templates that exist in `examples/templates/`. The base.json files are new, so review those, but don't spend any brain tokens on the correctness of the terraform and supporting files: any issues there are issues with the upstream example template
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
display_name: Azure VM (Linux)
|
||||
description: Provision Azure VMs as Coder workspaces
|
||||
icon: ../../../site/static/icon/azure.png
|
||||
maintainer_github: coder
|
||||
verified: true
|
||||
tags: [vm, linux, azure]
|
||||
---
|
||||
|
||||
# Remote Development on Azure VMs (Linux)
|
||||
|
||||
Provision Azure Linux VMs as [Coder workspaces](https://coder.com/docs/user-guides/workspace-management) with this example template.
|
||||
|
||||
<!-- prerequisites:start -->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Authentication
|
||||
|
||||
This template assumes that coderd is run in an environment that is authenticated
|
||||
with Azure. For example, run `az login` then `az account set --subscription=<id>`
|
||||
to import credentials on the system and user running coderd. For other ways to
|
||||
authenticate, [consult the Terraform docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure).
|
||||
|
||||
<!-- prerequisites:end -->
|
||||
|
||||
## Architecture
|
||||
|
||||
This template provisions the following resources:
|
||||
|
||||
- Azure VM (ephemeral, deleted on stop)
|
||||
- Managed disk (persistent, mounted to `/home/coder`)
|
||||
- Resource group, virtual network, subnet, and network interface (persistent, required by the managed disk and VM)
|
||||
|
||||
### What happens on stop
|
||||
|
||||
When a workspace is **stopped**, only the VM is destroyed. The managed disk, resource group, virtual network, subnet, and network interface all persist. This is by design. The managed disk retains your `/home/coder` data across workspace restarts, and the other resources remain because the disk depends on them.
|
||||
|
||||
This means you will see these Azure resources in your subscription even when a workspace is stopped. This is expected behavior.
|
||||
|
||||
### What happens on delete
|
||||
|
||||
When a workspace is **deleted**, all resources are destroyed, including the resource group, networking resources, and managed disk.
|
||||
|
||||
### Workspace restarts
|
||||
|
||||
Since the VM is ephemeral, any tools or files outside of the home directory are not persisted across restarts. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). Alternatively, individual developers can [personalize](https://coder.com/docs/user-guides/workspace-dotfiles) their workspaces with dotfiles.
|
||||
|
||||
> [!NOTE]
|
||||
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "azure-linux",
|
||||
"display_name": "Azure VM (Linux)",
|
||||
"os": "linux",
|
||||
"default_context": {}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#cloud-config
|
||||
cloud_final_modules:
|
||||
- [scripts-user, always]
|
||||
bootcmd:
|
||||
# work around https://github.com/hashicorp/terraform-provider-azurerm/issues/6117
|
||||
- until [ -e /dev/disk/azure/scsi1/lun10 ]; do sleep 1; done
|
||||
device_aliases:
|
||||
homedir: /dev/disk/azure/scsi1/lun10
|
||||
disk_setup:
|
||||
homedir:
|
||||
table_type: gpt
|
||||
layout: true
|
||||
fs_setup:
|
||||
- label: coder_home
|
||||
filesystem: ext4
|
||||
device: homedir.1
|
||||
mounts:
|
||||
- ["LABEL=coder_home", "/home/${username}"]
|
||||
hostname: ${hostname}
|
||||
users:
|
||||
- name: ${username}
|
||||
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
packages:
|
||||
- git
|
||||
write_files:
|
||||
- path: /opt/coder/init
|
||||
permissions: "0755"
|
||||
encoding: b64
|
||||
content: ${init_script}
|
||||
- path: /etc/systemd/system/coder-agent.service
|
||||
permissions: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Coder Agent
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=${username}
|
||||
ExecStart=/opt/coder/init
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
TimeoutStopSec=90
|
||||
KillMode=process
|
||||
|
||||
OOMScoreAdjust=-900
|
||||
SyslogIdentifier=coder-agent
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
runcmd:
|
||||
- chown ${username}:${username} /home/${username}
|
||||
- systemctl enable coder-agent
|
||||
- systemctl start coder-agent
|
||||
@@ -0,0 +1,293 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
}
|
||||
cloudinit = {
|
||||
source = "hashicorp/cloudinit"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# See https://registry.coder.com/modules/coder/azure-region
|
||||
module "azure_region" {
|
||||
source = "registry.coder.com/coder/azure-region/coder"
|
||||
|
||||
# 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.
|
||||
version = "~> 1.0"
|
||||
|
||||
default = "eastus"
|
||||
}
|
||||
|
||||
data "coder_parameter" "instance_type" {
|
||||
name = "instance_type"
|
||||
display_name = "Instance type"
|
||||
description = "What instance type should your workspace use?"
|
||||
default = "Standard_B4ms"
|
||||
icon = "/icon/azure.png"
|
||||
mutable = false
|
||||
option {
|
||||
name = "Standard_B1ms (1 vCPU, 2 GiB RAM)"
|
||||
value = "Standard_B1ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B2ms (2 vCPU, 8 GiB RAM)"
|
||||
value = "Standard_B2ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B4ms (4 vCPU, 16 GiB RAM)"
|
||||
value = "Standard_B4ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B8ms (8 vCPU, 32 GiB RAM)"
|
||||
value = "Standard_B8ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B12ms (12 vCPU, 48 GiB RAM)"
|
||||
value = "Standard_B12ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_B16ms (16 vCPU, 64 GiB RAM)"
|
||||
value = "Standard_B16ms"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D2as_v5 (2 vCPU, 8 GiB RAM)"
|
||||
value = "Standard_D2as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D4as_v5 (4 vCPU, 16 GiB RAM)"
|
||||
value = "Standard_D4as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D8as_v5 (8 vCPU, 32 GiB RAM)"
|
||||
value = "Standard_D8as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D16as_v5 (16 vCPU, 64 GiB RAM)"
|
||||
value = "Standard_D16as_v5"
|
||||
}
|
||||
option {
|
||||
name = "Standard_D32as_v5 (32 vCPU, 128 GiB RAM)"
|
||||
value = "Standard_D32as_v5"
|
||||
}
|
||||
}
|
||||
|
||||
data "coder_parameter" "home_size" {
|
||||
name = "home_size"
|
||||
display_name = "Home volume size"
|
||||
description = "How large would you like your home volume to be (in GB)?"
|
||||
default = 20
|
||||
type = "number"
|
||||
icon = "/icon/azure.png"
|
||||
mutable = false
|
||||
validation {
|
||||
min = 1
|
||||
max = 1024
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
arch = "amd64"
|
||||
os = "linux"
|
||||
auth = "azure-instance-identity"
|
||||
|
||||
metadata {
|
||||
key = "cpu"
|
||||
display_name = "CPU Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "memory"
|
||||
display_name = "Memory Usage"
|
||||
interval = 5
|
||||
timeout = 5
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
free -m | awk 'NR==2{printf "%.2f%%\t", $3*100/$2 }'
|
||||
EOT
|
||||
}
|
||||
metadata {
|
||||
key = "disk"
|
||||
display_name = "Disk Usage"
|
||||
interval = 600 # every 10 minutes
|
||||
timeout = 30 # df can take a while on large filesystems
|
||||
script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -e
|
||||
df /home/coder | awk '$NF=="/"{printf "%s", $5}'
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
prefix = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
|
||||
}
|
||||
|
||||
data "cloudinit_config" "user_data" {
|
||||
gzip = false
|
||||
base64_encode = true
|
||||
|
||||
boundary = "//"
|
||||
|
||||
part {
|
||||
filename = "cloud-config.yaml"
|
||||
content_type = "text/cloud-config"
|
||||
|
||||
content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
|
||||
username = "coder" # Ensure this user/group does not exist in your VM image
|
||||
init_script = base64encode(coder_agent.main.init_script)
|
||||
hostname = lower(data.coder_workspace.me.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_resource_group" "main" {
|
||||
name = "${local.prefix}-resources"
|
||||
location = module.azure_region.value
|
||||
|
||||
tags = {
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment here and in the azurerm_network_interface resource to obtain a public IP
|
||||
#resource "azurerm_public_ip" "main" {
|
||||
# name = "publicip"
|
||||
# resource_group_name = azurerm_resource_group.main.name
|
||||
# location = azurerm_resource_group.main.location
|
||||
# allocation_method = "Static"
|
||||
#
|
||||
# tags = {
|
||||
# Coder_Provisioned = "true"
|
||||
# }
|
||||
#}
|
||||
|
||||
resource "azurerm_virtual_network" "main" {
|
||||
name = "network"
|
||||
address_space = ["10.0.0.0/24"]
|
||||
location = azurerm_resource_group.main.location
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
|
||||
tags = {
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_subnet" "internal" {
|
||||
name = "internal"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
virtual_network_name = azurerm_virtual_network.main.name
|
||||
address_prefixes = ["10.0.0.0/29"]
|
||||
}
|
||||
|
||||
resource "azurerm_network_interface" "main" {
|
||||
name = "nic"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = azurerm_resource_group.main.location
|
||||
|
||||
ip_configuration {
|
||||
name = "internal"
|
||||
subnet_id = azurerm_subnet.internal.id
|
||||
private_ip_address_allocation = "Dynamic"
|
||||
// Uncomment for public IP address as well as azurerm_public_ip resource above
|
||||
//public_ip_address_id = azurerm_public_ip.main.id
|
||||
}
|
||||
|
||||
tags = {
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_managed_disk" "home" {
|
||||
create_option = "Empty"
|
||||
location = azurerm_resource_group.main.location
|
||||
name = "home"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
storage_account_type = "StandardSSD_LRS"
|
||||
disk_size_gb = data.coder_parameter.home_size.value
|
||||
}
|
||||
|
||||
// azurerm requires an SSH key (or password) for an admin user or it won't start a VM. However,
|
||||
// cloud-init overwrites this anyway, so we'll just use a dummy SSH key.
|
||||
resource "tls_private_key" "dummy" {
|
||||
algorithm = "RSA"
|
||||
rsa_bits = 4096
|
||||
}
|
||||
|
||||
resource "azurerm_linux_virtual_machine" "main" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
name = "vm"
|
||||
resource_group_name = azurerm_resource_group.main.name
|
||||
location = azurerm_resource_group.main.location
|
||||
size = data.coder_parameter.instance_type.value
|
||||
// cloud-init overwrites this, so the value here doesn't matter
|
||||
admin_username = "adminuser"
|
||||
admin_ssh_key {
|
||||
public_key = tls_private_key.dummy.public_key_openssh
|
||||
username = "adminuser"
|
||||
}
|
||||
|
||||
network_interface_ids = [
|
||||
azurerm_network_interface.main.id,
|
||||
]
|
||||
computer_name = lower(data.coder_workspace.me.name)
|
||||
os_disk {
|
||||
caching = "ReadWrite"
|
||||
storage_account_type = "Standard_LRS"
|
||||
}
|
||||
source_image_reference {
|
||||
publisher = "Canonical"
|
||||
offer = "0001-com-ubuntu-server-focal"
|
||||
sku = "20_04-lts-gen2"
|
||||
version = "latest"
|
||||
}
|
||||
user_data = data.cloudinit_config.user_data.rendered
|
||||
|
||||
tags = {
|
||||
Coder_Provisioned = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_virtual_machine_data_disk_attachment" "home" {
|
||||
count = data.coder_workspace.me.transition == "start" ? 1 : 0
|
||||
managed_disk_id = azurerm_managed_disk.home.id
|
||||
virtual_machine_id = azurerm_linux_virtual_machine.main[0].id
|
||||
lun = "10"
|
||||
caching = "ReadWrite"
|
||||
}
|
||||
|
||||
resource "coder_metadata" "workspace_info" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
resource_id = azurerm_linux_virtual_machine.main[0].id
|
||||
|
||||
item {
|
||||
key = "type"
|
||||
value = azurerm_linux_virtual_machine.main[0].size
|
||||
}
|
||||
}
|
||||
|
||||
resource "coder_metadata" "home_info" {
|
||||
resource_id = azurerm_managed_disk.home.id
|
||||
|
||||
item {
|
||||
key = "size"
|
||||
value = "${data.coder_parameter.home_size.value} GiB"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user