前述
最近有一个个人需求,想要自建一个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等插件的登录态。
- 官方的VSCode Web module是直接使用的
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了。
文章评论