心 空

  • Tools
Ricky Hao
  1. 首页
  2. Linux
  3. 正文

如何自建Coder平台,并且添加VSCode Web

25 2 月, 2025 146点热度 0人点赞 0条评论

前述

最近有一个个人需求,想要自建一个VSCode Web的平台。
调研了一圈,发现自建一个Coder平台,然后配上VSCode Web是比较好的一个选择。

  • VSCode Web可以最简单的使用Code CLI,使用code serve-web搭建一个本地的VSCode Web,但是做不到环境隔离。
  • Coder的code-server的问题是,它不能接入微软的VS Extension商店,所以很多VSCode上的插件都用不了,比如C# Dev Kit。

使用Docker-Compose搭建

这里给一个最简单的范例docker-compose.yml:

  postgres:
    image: postgres:17.4
    container_name: postgres
    restart: unless-stopped
    shm_size: 256mb
    environment:
      - POSTGRES_USER={{username}}
      - POSTGRES_PASSWORD={{password}}
      - TZ=Asia/Shanghai
    volumes:
      - ./postgres:/var/lib/postgresql/data
    ports:
      - 5432:5432

  adminer:
    image: adminer
    container_name: adminer
    restart: unless-stopped

  registry:
    image: registry:2
    container_name: registry
    restart: unless-stopped
    user: 1000:1000
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./registry:/var/lib/registry
    ports:
      - 5000:5000

  coder:
    image: ghcr.io/coder/coder:latest
    restart: unless-stopped
    container_name: coder
    environment:
      - TZ=Asia/Shanghai
      - CODER_PG_CONNECTION_URL={{postgresql_url}}
      - CODER_HTTP_ADDRESS=0.0.0.0:8080
      - CODER_ACCESS_URL={{https://coder.yourdomain.com}}
      - CODER_DERP_SERVER_STUN_ADDRESSES=stun.miwifi.com:3478
      - CODER_WILDCARD_ACCESS_URL={{*.coder.yourdomain.com}}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./coder:/home/coder

这个配置包含了四个镜像

  • postgresql: PostgreSQL数据库,用于Coder。
  • adminer: 一个简单的PHP数据库面板,用于在PostgreSQL里建立Coder的数据库。
  • registry: 一个简单的Docker镜像库,用于Coder build workspace时缓存镜像,加速build时间。
  • coder: Coder平台。
    • 这里/var/run/docker.sock传到容器里是为了让Coder能够利用Host的Docker开容器,如果遇到权限问题请Google一下。
    • CODER_WILDCARD_ACCESS_URL这个环境变量是为了支持后续给每个Workspace一个Subdomain,更好地隔离各个Workspace实例的Cookies。

使用Nginx转发

我用的Host上的Nginx给Coder做了一个转发服务,这里需要注意必须支持WebSocket的转发。
文档可以参考: Use NGINX as a Reverse Proxy。

配置Coder上的Template

现在我们访问刚刚搭建好的平台,注册Admin账号。
切换到Template页面,选择Docker (Devcontainer)来建立一个最初的Template。

在建好Template之后,我们需要将其修改一下,添加VSCode Web module,做一些其他的操作。
右上角点击,选择Edit files进入编辑。

在下面这个配置里,我对原配置做了一些修改:

  • 增加了Github Author Username和Github Author email的输入,自动配置git config。
  • 增加了Custom Repo URL,自动git clone。
  • 增加了SSH Private Key的透传,自动将Coder Account页面的SSH Private透传到Workspace,方便使用git ssh进行clone。
  • 增加了VSCode Web的module,这里使用了我自己魔改的VSCode Web module。
    • 官方的VSCode Web module是直接使用的code-server,会因为没有/mint-key服务导致在网页端只能使用In-Memory的储存,不能持久化Github等插件的登录态。
terraform {
  required_providers {
    coder = {
      source  = "coder/coder"
      version = "~> 1.0.0"
    }
    docker = {
      source = "kreuzwerker/docker"
    }
    envbuilder = {
      source = "coder/envbuilder"
    }
  }
}

variable "docker_socket" {
  default     = ""
  description = "(Optional) Docker socket URI"
  type        = string
}

provider "coder" {}
provider "docker" {
  # Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
  host = var.docker_socket != "" ? var.docker_socket : null
}
provider "envbuilder" {}

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

data "coder_parameter" "git_author_name" {
  default      = ""
  description  = "Required git author name."
  display_name = "Git Author name"
  name         = "git_author_name"
  mutable      = true
  order        = 1
}

data "coder_parameter" "git_author_email" {
  default      = ""
  description  = "Required git author email."
  display_name = "Git Author email"
  name         = "git_author_email"
  mutable      = true
  order        = 2
}

data "coder_parameter" "repo_url" {
  default      = ""
  description  = "Required repository URL."
  display_name = "Repository URL"
  name         = "repo_url"
  mutable      = true
  order        = 3
}

data "coder_parameter" "devcontainer_builder" {
  description  = <<-EOF
Image that will build the devcontainer.
We highly recommend using a specific release as the `:latest` tag will change.
Find the latest version of Envbuilder here: https://github.com/coder/envbuilder/pkgs/container/envbuilder
EOF
  display_name = "Devcontainer Builder"
  mutable      = true
  name         = "devcontainer_builder"
  default      = "ghcr.io/coder/envbuilder:latest"
  order        = 4
}

variable "cache_repo" {
  default     = ""
  description = "(Optional) Use a container registry as a cache to speed up builds."
  type        = string
}

variable "insecure_cache_repo" {
  default     = false
  description = "Enable this option if your cache registry does not serve HTTPS."
  type        = bool
}

variable "cache_repo_docker_config_path" {
  default     = ""
  description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required."
  sensitive   = true
  type        = string
}

locals {
  container_name             = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
  devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
  git_author_name            = data.coder_parameter.git_author_name.value
  git_author_email           = data.coder_parameter.git_author_email.value
  repo_url                   = data.coder_parameter.repo_url.value
  # The envbuilder provider requires a key-value map of environment variables.
  envbuilder_env = {
    # ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
    # if the cache repo is enabled.
    "ENVBUILDER_GIT_URL" : local.repo_url,
    "ENVBUILDER_CACHE_REPO" : var.cache_repo,
    "CODER_AGENT_TOKEN" : coder_agent.main.token,
    # Use the docker gateway if the access URL is 127.0.0.1
    "CODER_AGENT_URL" : replace(data.coder_workspace.me.access_url, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
    # Use the docker gateway if the access URL is 127.0.0.1
    "ENVBUILDER_INIT_SCRIPT" : replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal"),
    #"ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
    "ENVBUILDER_DOCKER_CONFIG_BASE64" : try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, ""),
    "ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
    "ENVBUILDER_GIT_SSH_PRIVATE_KEY_BASE64": base64encode(data.coder_workspace_owner.me.ssh_private_key),
    "ENVBUILDER_INSECURE" : "${var.insecure_cache_repo}",
  }
  # Convert the above map to the format expected by the docker provider.
  docker_env = [
    for k, v in local.envbuilder_env : "${k}=${v}"
  ]
}

data "local_sensitive_file" "cache_repo_dockerconfigjson" {
  count    = var.cache_repo_docker_config_path == "" ? 0 : 1
  filename = var.cache_repo_docker_config_path
}

resource "docker_image" "devcontainer_builder_image" {
  name         = local.devcontainer_builder_image
  keep_locally = true
}

resource "docker_volume" "workspaces" {
  name = "coder-${data.coder_workspace.me.id}"
  # Protect the volume from being deleted due to changes in attributes.
  lifecycle {
    ignore_changes = all
  }
  # Add labels in Docker to keep track of orphan resources.
  labels {
    label = "coder.owner"
    value = data.coder_workspace_owner.me.name
  }
  labels {
    label = "coder.owner_id"
    value = data.coder_workspace_owner.me.id
  }
  labels {
    label = "coder.workspace_id"
    value = data.coder_workspace.me.id
  }
  # This field becomes outdated if the workspace is renamed but can
  # be useful for debugging or cleaning out dangling volumes.
  labels {
    label = "coder.workspace_name_at_creation"
    value = data.coder_workspace.me.name
  }
}

# Check for the presence of a prebuilt image in the cache repo
# that we can use instead.
resource "envbuilder_cached_image" "cached" {
  count         = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
  builder_image = local.devcontainer_builder_image
  git_url       = local.repo_url
  cache_repo    = var.cache_repo
  extra_env     = local.envbuilder_env
  insecure      = var.insecure_cache_repo
  cache_ttl_days  = 90
}

resource "docker_container" "workspace" {
  count = data.coder_workspace.me.start_count
  image = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
  # Uses lower() to avoid Docker restriction on container names.
  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
  # Hostname makes the shell more user friendly: coder@my-workspace:~$
  hostname = data.coder_workspace.me.name
  # Use the environment specified by the envbuilder provider, if available.
  #env = var.cache_repo == "" ? local.docker_env : envbuilder_cached_image.cached.0.env
  env = local.docker_env

  # network_mode = "host" # Uncomment if testing with a registry running on `localhost`.
  host {
    host = "host.docker.internal"
    ip   = "host-gateway"
  }
  volumes {
    container_path = "/workspaces"
    volume_name    = docker_volume.workspaces.name
    read_only      = false
  }

  # Add labels in Docker to keep track of orphan resources.
  labels {
    label = "coder.owner"
    value = data.coder_workspace_owner.me.name
  }
  labels {
    label = "coder.owner_id"
    value = data.coder_workspace_owner.me.id
  }
  labels {
    label = "coder.workspace_id"
    value = data.coder_workspace.me.id
  }
  labels {
    label = "coder.workspace_name"
    value = data.coder_workspace.me.name
  }
}

resource "coder_agent" "main" {
  arch           = data.coder_provisioner.me.arch
  os             = "linux"
  startup_script = <<-EOT
    set -e

    # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
  EOT
  dir            = local.repo_url != "" ? "/workspaces/${replace(regex("([^\\/]+)(\\.git)?$", local.repo_url)[0], ".git", "")}" : "/workspaces"

  # These environment variables allow you to make Git commits right away after creating a
  # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
  # You can remove this block if you'd prefer to configure Git manually or using
  # dotfiles. (see docs/dotfiles.md)
  env = {
    GIT_AUTHOR_NAME     = local.git_author_name
    GIT_AUTHOR_EMAIL    = local.git_author_email
    GIT_COMMITTER_NAME  = local.git_author_name
    GIT_COMMITTER_EMAIL = local.git_author_email
  }

  # 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
  }

  metadata {
    display_name = "Swap Usage (Host)"
    key          = "7_swap_host"
    script       = <<EOT
      free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
    EOT
    interval     = 10
    timeout      = 1
  }
}

module "vscode-web" {
  count          = data.coder_workspace.me.start_count
  #source         = "registry.coder.com/modules/vscode-web/coder"
  source         = "git::https://github.com/Ricky-Hao/coder-modules.git//vscode-web?ref=main"
  #version        = "1.0.30"
  agent_id       = coder_agent.main.id
  slug           = "vsc"
  accept_license = true
  order          = 1
  folder         = local.repo_url != "" ? "/workspaces/${replace(regex("([^\\/]+)(\\.git)?$", local.repo_url)[0], ".git", "")}" : "/workspaces"
  subdomain      = true
  auto_install_extensions = true
  settings       = {
    "workbench.colorTheme" = "Visual Studio Dark",
    "vim.useCtrlKeys" = false,
    "git.autofetch" = true,
    "editor.inlineSuggest.enabled" = true,
    "github.copilot.enable" = {
      "*" = true,
      "plaintext" = false,
      "markdown" = true,
      "scminput" = false,
      "yaml" = false,
      "json" = true
    },
    "[python]" = {
      "editor.formatOnType" = true
    },
    "csharp.preview.improvedLaunchExperience" = true,
    "editor.formatOnPaste" = true,
    "editor.formatOnSave" = true,
    "editor.formatOnType" = true,
    "dotnet.formatting.organizeImportsOnFormat" = true,
    "diffEditor.ignoreTrimWhitespace" = false,
  }
}

resource "coder_metadata" "container_info" {
  count       = data.coder_workspace.me.start_count
  resource_id = coder_agent.main.id
  item {
    key   = "workspace image"
    value = var.cache_repo == "" ? local.devcontainer_builder_image : envbuilder_cached_image.cached.0.image
  }
  item {
    key   = "git url"
    value = local.repo_url
  }
  item {
    key   = "cache repo"
    value = var.cache_repo == "" ? "not enabled" : var.cache_repo
  }
}

建立Workspace

编辑完Template之后,点右上角Build,完成后就可以保存为新版本。
现在可以用这个Template来建立新的Workspace了。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: docker github linux other
最后更新:25 2 月, 2025

Ricky

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

COPYRIGHT © 2025 心 空. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang