230 lines
11 KiB
Docker
230 lines
11 KiB
Docker
# 使用本地缓存资源的 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 <arno@arnojin.com>" \
|
||
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"]
|