# 使用本地缓存资源的 Dockerfile # 构建前请先运行 ./download-resources.sh 下载所需资源 # 注意:默认情况下 .env 文件对 Dockerfile 是无效的 # ============================================================================ # 基础镜像: 全局环境变量和标签 # ============================================================================ FROM mcr.microsoft.com/devcontainers/base:noble AS base_0_5 ARG CONTAINER_USER_UID=1000 ARG CONTAINER_USER_GID=1000 ARG PROJECT_NAME=isos ARG DOCKER_GID=984 ENV TZ="Asia/Shanghai" \ CONTAINER_USER_UID="$CONTAINER_USER_UID" \ CONTAINER_USER_GID="$CONTAINER_USER_GID" \ PROJECT_NAME="$PROJECT_NAME" \ DOCKER_GID="$DOCKER_GID" \ PROJECT_ROOT="/workspace" \ DEBIAN_FRONTEND=noninteractive LABEL maintainer="Arno Jin " \ base.image="mcr.microsoft.com/devcontainers/base:noble" \ ubuntu.version="24.04" \ uv.version="installed" \ spec-kit.version="installed" \ container.name="$PROJECT_NAME" \ container.hostname="$PROJECT_NAME" # ============================================================================ # 阶段 1/4: 系统级安装 + Docker CLI(需要 root 权限) # ============================================================================ # 注意: mcr.microsoft.com/devcontainers/base:noble 已预装: # 核心: git, curl, wget, sudo, jq, unzip, zip, gnupg, xz-utils, patch # 编辑: vim-tiny, vim-common, nano # 构建: build-essential (gcc, g++, make), libc6-dev, libssl-dev, zlib1g-dev # 网络: iproute2, net-tools, openssh-client # 系统: locales, tzdata, ca-certificates, htop, strace, lsof, ncdu, tree, rsync # Shell: bash, zsh (+ oh-my-zsh) # 用户: vscode (UID/GID 1000, sudo NOPASSWD, shell=/bin/bash) # Root: shell=/bin/bash # 以下工具/库需要额外安装: FROM base_0_5 AS system_and_docker_1_4 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ set -eu && \ # 配置清华镜像源 sed -i 's|http://archive.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \ && sed -i 's|http://security.ubuntu.com|https://mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/ubuntu.sources \ # 移除失效的 Yarn APT 源 && rm -f /etc/apt/sources.list.d/yarn.list \ /usr/share/keyrings/yarn-archive-keyring.gpg \ /etc/apt/sources.list.d/yarn.list.bak 2>/dev/null || true \ # 安装系统包 && apt-get update \ && apt-get install -y --no-install-recommends \ bats tmux ssh ipset iptables \ iputils-ping dnsutils telnet fzf vim xvfb xauth x11-utils \ fonts-inter fonts-noto-cjk fonts-jetbrains-mono fonts-liberation fontconfig fonts-noto-color-emoji \ # Playwright Chromium 核心依赖 libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libatspi2.0-0t64 \ libdrm2 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxext6 \ libgbm1 libxkbcommon0 libpango-1.0-0 libcairo2 libcairo-gobject2 \ libasound2t64 libcups2t64 libgtk-3-0t64 libvulkan1 xdg-utils \ # Playwright Chromium X11/Wayland 依赖 libdbus-1-3 libx11-6 libxcb1 libxcursor1 libxi6 libxrender1 \ libx11-xcb1 libxcb-shm0 libglib2.0-0t64 \ # Playwright Chromium 渲染/媒体依赖 libfontconfig1 libfreetype6 libgdk-pixbuf-2.0-0 libpangocairo-1.0-0 \ libavcodec60 libopus0 libvpx9 libwebp7 libxslt1.1 libharfbuzz0b \ libjpeg-turbo8 libpng16-16t64 libavif16 liblcms2-2 libevent-2.1-7t64 \ # GPU 支持 libgl1 libegl1 \ # --- Docker CLI(DooD 模式,仅 CLI,不含 daemon,使用清华镜像源)--- && install -m 0755 -d /etc/apt/keyrings \ && curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ && chmod a+r /etc/apt/keyrings/docker.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ | tee /etc/apt/sources.list.d/docker.list > /dev/null \ && apt-get update \ && apt-get install -y --no-install-recommends \ docker-ce-cli \ docker-buildx-plugin \ docker-compose-plugin \ # --- 系统配置 --- && locale-gen zh_CN.UTF-8 \ && update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 \ && echo "Asia/Shanghai" > /etc/timezone \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && fc-cache -fv \ && echo 'fs.inotify.max_user_watches=524288' >> /etc/sysctl.d/10-sysctl.conf \ && rm -rf /var/lib/apt/lists/* \ /tmp/* \ /var/tmp/* \ /var/cache/apt/archives/*.deb # ============================================================================ # 阶段 2/4: 安装 Chrome + 配置用户和目录权限(需要 root 权限) # ============================================================================ FROM system_and_docker_1_4 AS chrome_2_4 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=bind,source=.devcontainer/.cache/chrome,target=/tmp/chrome-cache \ set -eu && \ apt-get update \ && dpkg -i /tmp/chrome-cache/google-chrome-stable_*.deb || apt-get install -y -f \ && google-chrome --version \ && rm -rf /var/lib/apt/lists/* \ && if [ "$CONTAINER_USER_UID" != "1000" ] || [ "$CONTAINER_USER_GID" != "1000" ]; then \ groupmod -g "$CONTAINER_USER_GID" vscode && \ usermod -u "$CONTAINER_USER_UID" -d "/home/vscode" vscode && \ chown -R "$CONTAINER_USER_UID:$CONTAINER_USER_GID" /home/vscode; \ fi \ && groupmod -g ${DOCKER_GID} docker 2>/dev/null || groupadd -g ${DOCKER_GID} docker 2>/dev/null || true \ && usermod -aG docker vscode \ && mkdir -p $PROJECT_ROOT \ && chown -R vscode:vscode $PROJECT_ROOT # ============================================================================ # 阶段 3/4: 安装开发工具(普通用户) # ============================================================================ FROM chrome_2_4 AS dev_tools_3_4 ENV BIN_PATH="$PROJECT_ROOT/.devcontainer/.volumes/bin" \ NVM_DIR="/home/vscode/.nvm" \ NODE_VERSION="22.22.0" \ UV_PYTHON_DIR="/home/vscode/.local/share/uv" \ UV_BIN="/home/vscode/.local/bin" \ PATH="/home/vscode/.local/bin:$PATH" \ UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \ UV_LINK_MODE=copy \ PYTHONUNBUFFERED=1 \ COREPACK_ENABLE_DOWNLOAD_PROMPT=false \ LC_ALL=zh_CN.UTF-8 \ LC_TIME=zh_CN.UTF-8 \ LANG=zh_CN.UTF-8 \ DEVCONTAINER=true \ EDITOR=vim \ VISUAL=vim \ SHELL=/bin/bash \ DISPLAY="" WORKDIR $PROJECT_ROOT USER vscode SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] # 使用 --mount=type=bind 挂载缓存资源(避免 COPY 层残留临时文件) RUN --mount=type=bind,source=.devcontainer/.cache/uv,target=/tmp/uv-cache \ --mount=type=bind,source=.devcontainer/.cache/node,target=/tmp/node-cache \ --mount=type=bind,source=.devcontainer/.cache/npm,target=/tmp/npm-cache \ --mount=type=bind,source=.devcontainer/.cache/vscode,target=/tmp/vscode-cache \ --mount=type=bind,source=.devcontainer/.cache/nvm,target=/tmp/nvm-cache \ --mount=type=bind,source=.devcontainer/.cache/spec-kit,target=/tmp/spec-kit-cache \ --mount=type=bind,source=.devcontainer/.cache/jj,target=/tmp/jj-cache \ mkdir -p "$UV_BIN" "$UV_PYTHON_DIR" "$NVM_DIR" \ && tar -xzf /tmp/uv-cache/uv-x86_64-unknown-linux-gnu.tar.gz -C "$UV_BIN" --strip-components=1 \ && tar -xzf /tmp/uv-cache/cpython-*-x86_64-unknown-linux-gnu-install_only.tar.gz -C "$UV_PYTHON_DIR" \ && ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python3" \ && ln -sf "$UV_PYTHON_DIR/python/bin/python3.12" "$UV_BIN/python" \ && "$UV_BIN/uv" tool install --python "$UV_PYTHON_DIR/python/bin/python3.12" /tmp/spec-kit-cache \ && ln -sf "/home/vscode/.local/share/uv/tools/specify-cli/bin/specify" "$UV_BIN/specify" \ && cp -r /tmp/nvm-cache/. "$NVM_DIR/" \ && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" \ && mkdir -p "$NVM_DIR/versions/node" \ && tar -xJf /tmp/node-cache/node-v*.tar.xz -C "$NVM_DIR/versions/node" \ && NODE_DIR=$(ls -d "$NVM_DIR/versions/node"/node-v* | head -n1) \ && mv "$NODE_DIR" "$NVM_DIR/versions/node/v${NODE_VERSION}" \ && nvm use v${NODE_VERSION} \ && nvm alias default v${NODE_VERSION} \ && export PATH="$NVM_DIR/versions/node/v${NODE_VERSION}/bin:$PATH" \ && export npm_config_registry=https://registry.npmmirror.com \ && find /tmp/npm-cache/ -maxdepth 1 -name "npm-*.tgz" -exec npm install -g {} + \ && find /tmp/npm-cache/ -maxdepth 1 -name "*.tgz" ! -name "npm-*.tgz" -exec npm install -g {} + \ # 安装 VSCode 扩展 && mkdir -p "/home/vscode/.vscode-server/extensions" \ && for ext in /tmp/vscode-cache/*.vsix; do \ [ -f "$ext" ] || continue; \ ext_name=$(basename "$ext" .vsix); \ temp_dir=$(mktemp -d); \ unzip -q "$ext" -d "$temp_dir" 2>/dev/null || true; \ [ -d "$temp_dir/extension" ] || { rm -rf "$temp_dir"; continue; }; \ version=$(jq -r '.version' "$temp_dir/extension/package.json" 2>/dev/null || echo "0.0.0"); \ ext_dir="/home/vscode/.vscode-server/extensions/${ext_name}-${version}"; \ mkdir -p "$ext_dir"; \ cp -r "$temp_dir/extension"/* "$ext_dir/"; \ rm -rf "$temp_dir"; \ done \ # 安装 Jujutsu (jj) 版本控制工具 && jj_tar=$(ls /tmp/jj-cache/jj-*.tar.gz 2>/dev/null | head -n1) \ && if [ -n "$jj_tar" ]; then \ mkdir -p /tmp/jj-extract \ && tar -xzf "$jj_tar" -C /tmp/jj-extract \ && cp /tmp/jj-extract/jj "$UV_BIN/jj" \ && chmod +x "$UV_BIN/jj" \ && jj --version; \ fi # 将 Node.js 加入镜像级 PATH,确保非交互式进程也能找到 node/npm ENV PATH="/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin:${PATH}" # ============================================================================ # 阶段 4/4: 配置用户 shell 环境(.bashrc) # ============================================================================ FROM dev_tools_3_4 AS shell_env_4_4 SHELL ["/bin/bash", "-e", "-u", "-o", "pipefail", "-c"] # 修复 /usr/local/bin/code 包装脚本 USER root COPY .devcontainer/scripts/code-wrapper /usr/local/bin/code RUN chmod +x /usr/local/bin/code # 使用模板文件替代逐行 echo,通过 sed 替换占位符 COPY .devcontainer/templates/bashrc.tail.sh /tmp/bashrc.tail.sh RUN sed "s|{{NODE_PATH}}|/home/vscode/.nvm/versions/node/v${NODE_VERSION}/bin|g; \ s|{{BIN_PATH}}|${BIN_PATH}|g" \ /tmp/bashrc.tail.sh >> /home/vscode/.bashrc \ && rm -f /tmp/bashrc.tail.sh \ && chown vscode:vscode /home/vscode/.bashrc USER vscode RUN mkdir -p /home/vscode/.ssh /home/vscode/.claude \ && chmod 700 /home/vscode/.ssh CMD ["sleep", "infinity"]