Skip to content

chore(examples): add sample Incus template #12114

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 2 commits into from
Feb 13, 2024
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
49 changes: 49 additions & 0 deletions examples/templates/incus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: Incus System Container with Docker
description: Develop in an Incus System Container with Docker using incus
tags: [local, incus, lxc, lxd]
icon: /icon/lxc.svg
---

# Incus System Container with Docker

Develop in an Incus System Container and run nested Docker containers using Incus on your local infrastructure.

## Prerequisites

1. Install [Incus](https://linuxcontainers.org/incus/) on the same machine as Coder.
2. Allow Coder to access the Incus socket.

- If you're running Coder as system service, run `sudo usermod -aG incus coder` and restart the Coder service.
- If you're running Coder as a Docker Compose service, get the group ID of the `incus` group by running `getent group incus` and add the following to your `compose.yaml` file:

```yaml
services:
coder:
volumes:
- /var/lib/incus/unix.socket:/var/lib/incus/unix.socket
group_add:
- 997 # Replace with the group ID of the `incus` group
```

3. Create a storage pool named `coder` and `btrfs` as the driver by running `incus storage create coder btrfs`.

## Usage

> **Note:** this template requires using a container image with cloud-init installed such as `ubuntu/jammy/cloud/amd64`.

1. Run `coder templates init -id incus`
1. Select this template
1. Follow the on-screen instructions

## Extending this template

See the [lxd/incus](https://registry.terraform.io/providers/lxc/incus/latest/docs) Terraform provider documentation to
add the following features to your Coder template:

- HTTPS incus host
- Volume mounts
- Custom networks
- More

We also welcome contributions!
309 changes: 309 additions & 0 deletions examples/templates/incus/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
incus = {
source = "lxc/incus"
}
}
}

data "coder_provisioner" "me" {}

provider "incus" {}

data "coder_workspace" "me" {}

data "coder_parameter" "image" {
name = "image"
display_name = "Image"
description = "The container image to use. Be sure to use a variant with cloud-init installed!"
default = "ubuntu/jammy/cloud/amd64"
icon = "/icon/image.svg"
mutable = true
}

data "coder_parameter" "cpu" {
name = "cpu"
display_name = "CPU"
description = "The number of CPUs to allocate to the workspace (1-8)"
type = "number"
default = "1"
icon = "https://raw.githubusercontent.com/matifali/logos/main/cpu-3.svg"
mutable = true
validation {
min = 1
max = 8
}
}

data "coder_parameter" "memory" {
name = "memory"
display_name = "Memory"
description = "The amount of memory to allocate to the workspace in GB (up to 16GB)"
type = "number"
default = "2"
icon = "/icon/memory.svg"
mutable = true
validation {
min = 1
max = 16
}
}

data "coder_parameter" "git_repo" {
type = "string"
name = "Git repository"
default = "https://github.com/coder/coder"
description = "Clone a git repo into [base directory]"
mutable = true
}

data "coder_parameter" "repo_base_dir" {
type = "string"
name = "Repository Base Directory"
default = "~"
description = "The directory specified will be created (if missing) and the specified repo will be cloned into [base directory]/{repo}🪄."
mutable = true
}

resource "coder_agent" "main" {
count = data.coder_workspace.me.start_count
arch = data.coder_provisioner.me.arch
os = "linux"
dir = "/home/${local.workspace_user}"
env = {
CODER_WORKSPACE_ID = data.coder_workspace.me.id
}

metadata {
display_name = "CPU Usage"
key = "0_cpu_usage"
script = "coder stat cpu"
interval = 10
timeout = 1
}

metadata {
display_name = "RAM Usage"
key = "1_ram_usage"
script = "coder stat mem"
interval = 10
timeout = 1
}

metadata {
display_name = "Home Disk"
key = "3_home_disk"
script = "coder stat disk --path /home/${lower(data.coder_workspace.me.owner)}"
interval = 60
timeout = 1
}
}

module "git-clone" {
source = "registry.coder.com/modules/git-clone/coder"
version = "1.0.2"
agent_id = local.agent_id
url = data.coder_parameter.git_repo.value
base_dir = local.repo_base_dir
}

module "code-server" {
source = "registry.coder.com/modules/code-server/coder"
version = "1.0.2"
agent_id = local.agent_id
folder = local.repo_base_dir
}

module "filebrowser" {
source = "registry.coder.com/modules/filebrowser/coder"
version = "1.0.2"
agent_id = local.agent_id
}

module "coder-login" {
source = "registry.coder.com/modules/coder-login/coder"
version = "1.0.2"
agent_id = local.agent_id
}

resource "incus_volume" "home" {
name = "coder-${data.coder_workspace.me.id}-home"
pool = local.pool
}

resource "incus_volume" "docker" {
name = "coder-${data.coder_workspace.me.id}-docker"
pool = local.pool
}

resource "incus_cached_image" "image" {
source_remote = "images"
source_image = data.coder_parameter.image.value
}

resource "incus_instance_file" "agent_token" {
count = data.coder_workspace.me.start_count
instance = incus_instance.dev.name
content = <<EOF
CODER_AGENT_TOKEN=${local.agent_token}
EOF
create_directories = true
target_path = "/opt/coder/init.env"
}

resource "incus_instance" "dev" {
running = data.coder_workspace.me.start_count == 1
name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}"
image = incus_cached_image.image.fingerprint

config = {
"security.nesting" = true
"security.syscalls.intercept.mknod" = true
"security.syscalls.intercept.setxattr" = true
"boot.autostart" = true
"cloud-init.user-data" = <<EOF
#cloud-config
hostname: ${lower(data.coder_workspace.me.name)}
users:
- name: ${local.workspace_user}
uid: 1000
gid: 1000
groups: sudo
packages:
- curl
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
write_files:
- path: /opt/coder/init
permissions: "0755"
encoding: b64
content: ${base64encode(local.agent_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=${local.workspace_user}
EnvironmentFile=/opt/coder/init.env
ExecStart=/opt/coder/init
Restart=always
RestartSec=10
TimeoutStopSec=90
KillMode=process

OOMScoreAdjust=-900
SyslogIdentifier=coder-agent

[Install]
WantedBy=multi-user.target
- path: /etc/systemd/system/coder-agent-watcher.service
permissions: "0644"
content: |
[Unit]
Description=Coder Agent Watcher
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart coder-agent.service

[Install]
WantedBy=multi-user.target
- path: /etc/systemd/system/coder-agent-watcher.path
permissions: "0644"
content: |
[Path]
PathModified=/opt/coder/init.env
Unit=coder-agent-watcher.service

[Install]
WantedBy=multi-user.target
runcmd:
- chown -R ${local.workspace_user}:${local.workspace_user} /home/${local.workspace_user}
- |
#!/bin/bash
apt-get update && apt-get install -y curl docker.io
usermod -aG docker ${local.workspace_user}
newgrp docker
- systemctl enable coder-agent.service coder-agent-watcher.service coder-agent-watcher.path
- systemctl start coder-agent.service coder-agent-watcher.service coder-agent-watcher.path
EOF
}

limits = {
cpu = data.coder_parameter.cpu.value
memory = "${data.coder_parameter.cpu.value}GiB"
}

device {
name = "home"
type = "disk"
properties = {
path = "/home/${local.workspace_user}"
pool = local.pool
source = incus_volume.home.name
}
}

device {
name = "docker"
type = "disk"
properties = {
path = "/var/lib/docker"
pool = local.pool
source = incus_volume.docker.name
}
}

device {
name = "root"
type = "disk"
properties = {
path = "/"
pool = local.pool
}
}
}

locals {
workspace_user = lower(data.coder_workspace.me.owner)
pool = "coder"
repo_base_dir = data.coder_parameter.repo_base_dir.value == "~" ? "/home/${local.workspace_user}" : replace(data.coder_parameter.repo_base_dir.value, "/^~\\//", "/home/${local.workspace_user}/")
repo_dir = module.git-clone.repo_dir
agent_id = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].id : ""
agent_token = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].token : ""
agent_init_script = data.coder_workspace.me.start_count == 1 ? coder_agent.main[0].init_script : ""
}

resource "coder_metadata" "info" {
count = data.coder_workspace.me.start_count
resource_id = incus_instance.dev.name
item {
key = "memory"
value = incus_instance.dev.limits.memory
}
item {
key = "cpus"
value = incus_instance.dev.limits.cpu
}
item {
key = "instance"
value = incus_instance.dev.name
}
item {
key = "image"
value = "${incus_cached_image.image.source_remote}:${incus_cached_image.image.source_image}"
}
item {
key = "image_fingerprint"
value = substr(incus_cached_image.image.fingerprint, 0, 12)
}
}