Skip to content

Dev Container

本篇是基于devcontainers官方文档的简单翻译和实战教学。更多详细信息可以参考官方文档。

什么是 Dev Container

微软的 Dev Container(Development Container)是一种用于开发环境的标准化技术,旨在通过容器化技术(如 Docker)将一个预配置的开发环境,封装在容器中。它包含了开发所需的所有工具、依赖和配置(如编程语言运行时、SDK、调试工具、扩展等)。开发者可以通过简单的配置文件和容器技术快速启动一个一致的开发环境,避免“在我机器上能运行”的问题,同时支持跨平台开发(Windows、macOS、Linux)。

1. 主要组件

  • Docker 容器:提供隔离的运行环境。
  • VS Code 集成:通过 VS Code 的 Remote - Containers 扩展,直接在容器中开发和调试代码
  • Dev Container 配置文件:定义开发环境的配置(如工具、扩展、依赖)。

2. 优势

  • 一致性:所有开发者使用相同的环境,避免因环境差异导致的问题。
  • 可移植性:开发环境可以轻松共享和复用。
  • 隔离性:开发环境与本地系统隔离,避免污染本地环境。
  • 快速启动:通过预构建的镜像快速启动开发环境。

3. 局限性

  • 依赖 Docker:需要安装和运行 Docker,可能占用资源。
  • 学习成本:需要了解 Docker 和 Dev Container 的基本概念。
  • 性能开销:某些场景下(如文件 I/O)可能比本地开发慢。

注意

Windows Docker 系统基于 WSL2。由于文件系统差异。磁盘读写速度会比 Linux 系统慢。所以实际体验比 Mac OS差

为什么使用 Dev Container?

1. 团队协作

  • 确保团队成员使用相同的开发环境,减少配置差异带来的问题。
  • 新成员可以快速上手,无需手动配置环境。

2.跨平台开发

  • 在 Windows、macOS、Linux 上提供一致的开发体验。
  • 支持在本地或远程服务器上运行开发环境。

3.复杂项目

  • 对于依赖复杂(如需要特定版本的编译器、库或工具)的项目,Dev Container 可以简化环境配置。
  • 对陈年旧代码,也可以提供一个干净的环境进行修复和改进。

4.开源项目贡献

  • 开源项目可以提供 Dev Container 配置,方便贡献者快速搭建开发环境。

安装

首先你需要安装以下工具以确保你的电脑能够运行 Dev Container。官方的安装指南是一个非常好的起点。当然你也可以看下面的简单指南。

安装依赖

编辑器

Docker

可以通过官网下载Docker安装。也可以按照下面的链接:

注意

Windows需要安装WSL2

开始

接下来,我们将通过一个实际的项目(英孚)来展示如何使用 Dev Container 进行开发。

信息

由于在使用过程中vscode会通过终端运行拉取Docker镜像。那么请确保你的网络环境可以正常访问Docker Hub。并且在终端设置代理配置

获取/打开项目

打开一个项目。可以是本地的/远程的。这里以英孚为例。

创建配置文件

Dev Container 的核心是一个配置文件.devcontainer/devcontainer.json,它定义了开发环境的配置,例如:

  • 使用的 Docker 镜像或 Dockerfile。
  • 需要安装的 VS Code 扩展。
  • 环境变量、端口映射等。

创建.devcontainer文件夹

在项目根目录创建 .devcontainer 文件夹。

创建.devcontainer/Dockerfile

dockerfile
# ARG VARIANT=22-bookworm
# FROM mcr.microsoft.com/devcontainers/javascript-node:14
# 使用你需要的node版本
FROM node:14

ARG USERNAME=node
ARG NPM_GLOBAL=/usr/local/share/npm-global
ARG NPM_REGISTRY=https://registry.npmmirror.com

# 将 NPM 全局路径添加到 PATH 中。
ENV PATH=${NPM_GLOBAL}/bin:${PATH}

RUN \
  # Configure global npm install location, use group to adapt to UID/GID changes
  if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \
  && usermod -a -G npm ${USERNAME} \
  && umask 0002 \
  && mkdir -p ${NPM_GLOBAL} \
  && touch /usr/local/etc/npmrc \
  && chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \
  && chmod g+s ${NPM_GLOBAL} \
  && npm config -g set prefix ${NPM_GLOBAL} \
  && npm config -g set registry ${NPM_REGISTRY} \
  && su ${USERNAME} -c "npm config -g set prefix ${NPM_GLOBAL}" \
  && su ${USERNAME} -c "npm config -g set registry ${NPM_REGISTRY}" \
  # Install eslint
  && su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
  && npm cache clean --force > /dev/null 2>&1

#[可选] 取消注释此部分以安装额外的操作系统包。
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>


# [可选] 如果你想使用 nvm 安装额外的 Node 版本,请取消注释。
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"

# [可选] 如果你想安装更多的全局 Node 模块,请取消注释。
# RUN su node -c "npm install -g <your-package-list-here>"

# Install tslint, typescript. eslint is installed by javascript image
ARG NODE_MODULES="tslint-to-eslint-config typescript"
RUN su node -c "umask 0002 && npm install -g ${NODE_MODULES}" \
  && npm cache clean --force > /dev/null 2>&1

# [可选] 取消注释此部分以安装额外的操作系统包。
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

# [可选] 如果你想安装更多的全局 Node 包,请取消注释。
# RUN su node -c "npm install -g <your-package-list -here>"

.devcontainer/devcontainer.json 中写入:

json
{
  "name": "Node.js & TypeScript",
  // "image": "ghcr.io/devcontainers/templates/typescript-node:4.0.2", // 或者使用 "dockerFile" 字段
  "runArgs": ["--name=my-dev-container"], // 可以修改为自定义名称
  "build": {
    "dockerfile": "Dockerfile"
  },
  "features": {
    // "ghcr.io/devcontainers/features/git:1": {
    //   "version": "latest",
    //   "ppa": "false"
    // },
  },

  "mounts": [
    // "source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
    // "source=${localWorkspaceFolder}-dist,target=${containerWorkspaceFolder}/dist,type=volume"
  ],
  // Configure tool-specific properties.
  "customizations": {
    // Configure properties specific to VS Code.
    "vscode": {
      // Add the IDs of extensions you want installed when the container is created.
      "extensions": [
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint@2.4.4",
        "1814784429.wmeimob",
        "dbaeumer.vscode-eslint"
      ]
    }
  },

  // Use 'forwardPorts' to make a list of ports inside the container available locally.
  // "forwardPorts": [],

  // Use 'postCreateCommand' to run commands after the container is created.
  // "postCreateCommand": "yarn install",

  // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
  "remoteUser": "root"
}

启动 Dev Container

  • 重启Vs Code,编辑器会提示 在容器中重新打开。或者点击编辑器左下角的唤起菜单并选择 在容器中重新打开 按钮。

  • 如果你是首次打开。那么VS Code 会执行镜像拉取/构建/启动/下载安装相关依赖和插件等操作。此处会花费一定时间。请确保网络畅通已经正确设置了代理。

  • 如果已经构建过。那么Vs Code 会直接启动容器。

  • 正常情况下 你打开 Docker Desktop 或者通过命令行 查看正在运行的容器。

开发

容器启动后。你就可以在容器中进行开发了。开发环境已经配置好。你只需要编写代码即可。默认情况下,容器会自动挂载你的项目目录到容器中并自动保留启动需要监听的端口。一切看起来都像是在你本地开发。

其他

镜像版本替换

如果你的网络实在无法下载dockerHub的镜像。公司也提供了内部镜像版本。仅需要替换 FROM node:14 部分即可

  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-10
  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-12
  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-14
  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-16
  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-18
  • registry.cn-hangzhou.aliyuncs.com/wmemob-public/frontend:node-20

官方资源

其他文档

Q&A

在文章的最后。给大家留几个问题。希望你们能够思考并回答。如果你们能够回答出来。那么也就意味着你们就掌握了本章的知识内容以及知道如何在容器中进行开发。

Q: 如何在容器中安装新的依赖?

有如下几种方式:

一、📦 通过 Dockerfile 安装(推荐)

这是最规范的方式,适合长期依赖。直接修改 DevContainer 的 Dockerfile,用 RUN 指令执行安装命令。

Dockerfile
# 示例:安装 apt 包
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

# 示例:安装 Python 包
RUN pip install numpy pandas

# 示例:安装 Node.js 全局包
RUN npm install -g eslint

优点:依赖固化到镜像中,重建容器时无需重复安装。

二、⚙️ 利用 devcontainer.json 的 postCreateCommand

在容器首次创建后执行命令,适合临时或动态依赖(但每次重建容器都会执行)。

json
// devcontainer.json
{
  "postCreateCommand": "apt-get update && apt-get install -y wget || npm install -g prettier"
}

适用场景:需要容器启动后自动执行的额外操作(如初始化数据库)。

三、📂 项目本地依赖文件

对于语言特定的依赖(如 Python/Node.js),通过项目内的依赖文件管理:

  • Python: requirements.txt 或 pyproject.toml
Dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt
  • Node.js: package.json
Dockerfile
COPY package*.json .
RUN npm install

优点:与项目代码版本绑定,便于协作。

四、🔧 手动进入容器安装(临时调试)

通过 docker execVSCode 终端直接进入容器手动安装:

bash
# 进入容器终端
docker exec -it <容器名或ID> /bin/bash

# 安装软件(如 vim)
apt-get update && apt-get install -y vim

注意:此方式安装的依赖不会持久化(容器重建后丢失),仅适合临时测试。

五、✨ 使用 VSCode 的 "Features" 功能

VSCode 提供了预置的 DevContainer Features,可一键添加常见工具(如 Git、Docker CLI、Python 等)。

devcontainer.json 中配置:

json
{
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:1": {},
    "ghcr.io/devcontainers/features/node:1": {}
  }
}

**优点:**无需手动编写安装脚本,快速集成标准化工具。

⚠️ 常见问题处理

  • 缓存问题:安装前先更新包管理器缓存(如 apt-get update)。
  • 权限问题:如果容器以非 root 用户运行,可能需要 sudo:
Dockerfile
USER root
RUN apt-get install -y <包名>
USER vscode  # 切换回默认用户
  • 全局包安装:如 Node.js 全局包可能需要 --unsafe-perm:
Dockerfile
RUN npm install -g --unsafe-perm your-package

📝 总结:如何选择?

  • 长期依赖 → 修改 Dockerfile

  • 动态初始化 → postCreateCommand

  • 临时调试 → 手动进入容器安装

  • 快速集成工具 → VSCode Features

Q: 通过pnpm安装的依赖在脱离容器后能够正常运行吗?原因是什么?

在 DevContainer 中通过 pnpm 安装的前端项目依赖,脱离容器后大概率无法直接正常运行。以下是具体原因和解决方案:

🔍 根本原因分析

  • 1. 依赖路径隔离性

    • 容器内依赖存储位置
      pnpm 默认会将依赖硬链接到项目的 node_modules 目录,但硬链接的路径是基于容器内的文件系统(如 /workspace/project)。 当脱离容器后,宿主机的项目目录路径可能与容器内的路径不一致(例如宿主机的路径是 /Users/yourname/project),导致 node_modules 中的依赖引用失效。

    • 虚拟存储冲突 pnpm 的全局虚拟存储(默认在 ~/.pnpm-store)在容器内外是隔离的。容器内安装的依赖会存储在容器的虚拟存储目录中,脱离容器后宿主机无法访问这些文件。

  • 2. 跨平台二进制兼容性

    • 原生模块编译问题 如果项目中包含依赖原生模块的包(如 node-sass、sharp 等),这些模块在容器内会根据容器的操作系统和架构(如 Linux x64)编译。 当脱离容器后,如果宿主机是其他系统(如 macOS 或 Windows),这些二进制文件将无法直接运行。
  • 3.Node.js 环境版本差异

    • 如果容器内的 Node.js 版本与宿主机不一致(如容器用 Node 20,宿主机用 Node 18),可能会导致依赖不兼容或运行时报错。

结论:为确保可靠性,强烈建议将 DevContainer 作为唯一的开发和运行环境,避免跨环境操作。