Skip to content

feat: dynamic username template #261

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 8 commits into from
Jul 31, 2025
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
Binary file added registry/ericpaulsen/.images/avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions registry/ericpaulsen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
display_name: "Eric Paulsen"
bio: "Field CTO, EMEA @ Coder"
avatar_url: "./.images/avatar.png"
github: "ericpaulsen"
linkedin: "https://www.linkedin.com/in/ericpaulsen17" # Optional
website: "https://ericpaulsen.io" # Optional
support_email: "ericpaulsen@hey.com" # Optional
status: "community"
---

# Eric Paulsen

I'm Eric Paulsen, Coder's EMEA Field CTO based in London, originating from Miami.
Outside of working with our customers, I enjoy teaching myself things,
playing volleyball, and dabbling in a bit of DJing & photography.
51 changes: 51 additions & 0 deletions registry/ericpaulsen/templates/k8s-username/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
display_name: Kubernetes (Deployment) with Dynamic Username
description: Provision Kubernetes Deployments as Coder workspaces with your Username
icon: ../../../site/static/icon/k8s.png
verified: true
tags: [kubernetes, container, username]
---

# Remote development on Kubernetes with dynamic usernames

Provision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. This template
will run the workspace container as a non-root UID using your Coder username.

Here is the entrypoint logic in the template that enables Coder to source your username and write it to the Ubuntu operating system at start-up.

> These commands may differ if you run your workspace image with a distro other than Ubuntu.

```terraform
command = ["sh", "-c", <<EOF
# Create user and setup home directory
sudo useradd ${data.coder_workspace_owner.me.name} --home=/home/${data.coder_workspace_owner.me.name} --shell=/bin/bash --uid=1001 --user-group
sudo chown -R ${data.coder_workspace_owner.me.name}:${data.coder_workspace_owner.me.name} /home/${data.coder_workspace_owner.me.name}

# Switch to user and run agent
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${data.coder_workspace_owner.me.name} sh -c '${coder_agent.main.init_script}'
EOF
]
```

<!-- TODO: Add screenshot -->

## Prerequisites

### Infrastructure

**Cluster**: This template requires an existing Kubernetes cluster

**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself.

### Authentication

This template authenticates using a `~/.kube/config`, if present on the server, or via built-in authentication if the Coder provisioner is running on Kubernetes with an authorized ServiceAccount. To use another [authentication method](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication), edit the template.

## Architecture

This template provisions the following resources:

- Kubernetes Deployment (ephemeral)
- Kubernetes persistent volume claim (persistent on `/home/${username}`, where `${username}` is your Coder username)

This means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
327 changes: 327 additions & 0 deletions registry/ericpaulsen/templates/k8s-username/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}

provider "coder" {
}

variable "use_kubeconfig" {
type = bool
description = <<-EOF
Use host kubeconfig? (true/false)

Set this to false if the Coder host is itself running as a Pod on the same
Kubernetes cluster as you are deploying workspaces to.

Set this to true if the Coder host is running outside the Kubernetes cluster
for workspaces. A valid "~/.kube/config" must be present on the Coder host.
EOF
default = false
}

variable "namespace" {
type = string
description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
}

data "coder_parameter" "cpu" {
name = "cpu"
display_name = "CPU"
description = "The number of CPU cores"
default = "2"
icon = "/icon/memory.svg"
mutable = true
option {
name = "2 Cores"
value = "2"
}
option {
name = "4 Cores"
value = "4"
}
option {
name = "6 Cores"
value = "6"
}
option {
name = "8 Cores"
value = "8"
}
}

data "coder_parameter" "memory" {
name = "memory"
display_name = "Memory"
description = "The amount of memory in GB"
default = "2"
icon = "/icon/memory.svg"
mutable = true
option {
name = "2 GB"
value = "2"
}
option {
name = "4 GB"
value = "4"
}
option {
name = "6 GB"
value = "6"
}
option {
name = "8 GB"
value = "8"
}
}

data "coder_parameter" "home_disk_size" {
name = "home_disk_size"
display_name = "Home disk size"
description = "The size of the home disk in GB"
default = "10"
type = "number"
icon = "/emojis/1f4be.png"
mutable = false
validation {
min = 1
max = 99999
}
}

provider "kubernetes" {
# Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
}

data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.3.1"
agent_id = coder_agent.main.id
accept_license = true
}

resource "coder_agent" "main" {
os = "linux"
arch = "amd64"

# The following metadata blocks are optional. They are used to display
# information about your workspace in the dashboard. You can remove them
# if you don't want to display any information.
# For basic resources, you can use the `coder stat` command.
# If you need more control, you can write your own script.
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}"
interval = 60
timeout = 1
}

metadata {
display_name = "CPU Usage (Host)"
key = "4_cpu_usage_host"
script = "coder stat cpu --host"
interval = 10
timeout = 1
}

metadata {
display_name = "Memory Usage (Host)"
key = "5_mem_usage_host"
script = "coder stat mem --host"
interval = 10
timeout = 1
}

metadata {
display_name = "Load Average (Host)"
key = "6_load_host"
# get load avg scaled by number of cores
script = <<EOT
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
EOT
interval = 60
timeout = 1
}
}

resource "kubernetes_persistent_volume_claim" "home" {
metadata {
name = "coder-${data.coder_workspace.me.id}-home"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-pvc"
"app.kubernetes.io/instance" = "coder-pvc-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
//Coder-specific labels.
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}
wait_until_bound = false
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = "${data.coder_parameter.home_disk_size.value}Gi"
}
}
}
}

resource "kubernetes_deployment" "main" {
count = data.coder_workspace.me.start_count
depends_on = [
kubernetes_persistent_volume_claim.home
]
wait_for_rollout = false
metadata {
name = "coder-${data.coder_workspace.me.id}"
namespace = var.namespace
labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
annotations = {
"com.coder.user.email" = data.coder_workspace_owner.me.email
}
}

spec {
replicas = 1
selector {
match_labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
}
strategy {
type = "Recreate"
}

template {
metadata {
labels = {
"app.kubernetes.io/name" = "coder-workspace"
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
"app.kubernetes.io/part-of" = "coder"
"com.coder.resource" = "true"
"com.coder.workspace.id" = data.coder_workspace.me.id
"com.coder.workspace.name" = data.coder_workspace.me.name
"com.coder.user.id" = data.coder_workspace_owner.me.id
"com.coder.user.username" = data.coder_workspace_owner.me.name
}
}
spec {


container {
name = "dev"
image = "codercom/enterprise-base:ubuntu"
image_pull_policy = "Always"
command = ["sh", "-c", <<EOF
# Create user and setup home directory
sudo useradd ${lower(data.coder_workspace_owner.me.name)} --home=/home/${lower(data.coder_workspace_owner.me.name)} --shell=/bin/bash --uid=1001 --user-group
sudo chown -R ${lower(data.coder_workspace_owner.me.name)}:${lower(data.coder_workspace_owner.me.name)} /home/${lower(data.coder_workspace_owner.me.name)}

# Switch to user and run agent
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${lower(data.coder_workspace_owner.me.name)} sh -c '${coder_agent.main.init_script}'
EOF
]
env {
name = "CODER_AGENT_TOKEN"
value = coder_agent.main.token
}
resources {
requests = {
"cpu" = "250m"
"memory" = "512Mi"
}
limits = {
"cpu" = "${data.coder_parameter.cpu.value}"
"memory" = "${data.coder_parameter.memory.value}Gi"
}
}
volume_mount {
mount_path = "/home/${lower(data.coder_workspace_owner.me.name)}"
name = "home"
read_only = false
}
}

volume {
name = "home"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.home.metadata.0.name
read_only = false
}
}

affinity {
// This affinity attempts to spread out all workspace pods evenly across
// nodes.
pod_anti_affinity {
preferred_during_scheduling_ignored_during_execution {
weight = 1
pod_affinity_term {
topology_key = "kubernetes.io/hostname"
label_selector {
match_expressions {
key = "app.kubernetes.io/name"
operator = "In"
values = ["coder-workspace"]
}
}
}
}
}
}
}
}
}
}