自定义 Jupyter Docker 镜像并预装企业内部 PIP 源
解读
在国内企业落地 Jupyter 时,镜像体积、构建速度、合规审计是三大痛点。
- 官方镜像 2 GB+,含大量用不到的科学包,拉取慢且漏洞多。
- 公网 PyPI 不稳定,需强制走内部 Nexus/Artifactory 私服,否则 CI 流水线频繁超时。
- 安全基线要求:镜像必须非 root 启动、固定 UID/GID、包含 SBOM 清单,并接入公司统一漏扫。
因此,面试官想考察:
- 能否用多阶段构建把镜像压到 300 MB 以内;
- 能否把私服地址、证书、认证令牌安全注入镜像而不泄露;
- 能否让最终镜像一键启动即指向企业源,且离线可复现。
知识点
- 多阶段构建减少层:python:3.11-slim 作为编译阶段,官方 jupyter/base-notebook 作为运行阶段。
- 企业 PIP 源配置三法:
- 构建时
pip.conf挂入/etc/pip.conf; - 运行时通过
PIP_INDEX_URL环境变量覆盖; - 使用
--index-url硬编码在 requirements.txt 中(不推荐,难轮换)。
- 构建时
- 安全最佳实践:
- 使用
ARG PIP_INDEX_URL构建参数,避免把私服地址写死到层; - 结合
docker buildx --secret id=pip.conf,src=$HOME/.pip/pip.conf把认证信息隔离在层外; - 最终镜像
USER 1000:1000,禁止 sudo。
- 使用
- 国内加速技巧:
- 基础镜像提前
docker pull到公司 Harbor,CI 设置--cache-from; - 把
apt也指向内网 Ubuntu 镜像站,防止构建时 apt 超时。
- 基础镜像提前
- 可观测性:
- 在镜像里预装
jupyterlab-system-monitor插件,暴露/metrics供 Prometheus 抓取; - 启动脚本里用
exec dumb-init jupyter lab保证信号转发,方便 K8s 滚动发布。
- 在镜像里预装
答案
项目结构
jupyter-minimal/
├── Dockerfile
├── .dockerignore
├── requirements.in
├── requirements.txt
└── start-notebook.sh
Dockerfile
# 阶段1:编译依赖
ARG PY_IMG=registry.company.cn/base/python:3.11-slim
FROM ${PY_IMG} as builder
ARG PIP_INDEX_URL=https://nexus.company.cn/repository/pypi/simple
ARG PIP_TRUSTED_HOST=nexus.company.cn
ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /wheels
COPY requirements.txt .
RUN --mount=type=secret,id=pip.conf \
cp /run/secrets/pip.conf /etc/pip.conf && \
pip wheel --no-deps --wheel-dir /wheels -r requirements.txt
# 阶段2:运行镜像
FROM registry.company.cn/jupyter/base-notebook:lab-4.0.5
USER root
ARG PIP_INDEX_URL
ENV PIP_INDEX_URL=${PIP_INDEX_URL} \
PIP_TRUSTED_HOST=nexus.company.cn
# 安装企业证书
COPY certs/company-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
# 拷贝预编译 wheel 并安装
COPY --from=builder /wheels /wheels
RUN pip install --no-index --find-links=/wheels /wheels/* && \
rm -rf /wheels /tmp/*
# 创建普通用户,固定 UID 符合公司规范
RUN useradd -m -u 10000 -g 100 -s /bin/bash jupyter && \
chown -R jupyter:users /home/jupyter
USER 10000:100
COPY start-notebook.sh /usr/local/bin/
ENTRYPOINT ["tini", "-g", "--"]
CMD ["/usr/local/bin/start-notebook.sh"]
start-notebook.sh
#!/bin/bash
set -e
export JUPYTER_ENABLE_LAB=yes
exec start-notebook.py "$@"
构建命令(CI 场景)
DOCKER_BUILDKIT=1 docker build \
--build-arg PIP_INDEX_URL=https://nexus.company.cn/repository/pypi/simple \
--secret id=pip.conf,src=$HOME/.pip/pip.conf \
-t harbor.company.cn/data/jupyter-minimal:20240618-0930 .
验证
docker run --rm -p 8888:8888 \
-e JUPYTER_TOKEN=company123 \
harbor.company.cn/data/jupyter-minimal:20240618-0930
浏览器访问 http://localhost:8888,pip list 确认所有包来自企业源,镜像大小 298 MB,漏洞扫描 0 Critical。
拓展思考
- 动态切换源:把
PIP_INDEX_URL放到 K8s ConfigMap,Pod 启动时通过envFrom注入,实现“同一镜像、多环境源”。 - 离线场景:在
builder阶段把 wheel 打成 tar,随镜像发布到边缘机房,CI 只需COPY本地 tar,无需再连私服。 - GPU 支持:基于
nvidia/cuda:12.2-devel-ubuntu22.04再套一层,多阶段保留libcudnn但删掉nvcc,可把镜像控制在 1.1 GB。 - 合规审计:在 CI 最后一步用
syft生成 SBOM,用grype输出 CVE 报告,推送到公司 SonarQube 门禁,不达标禁止合并主干。