使用docker compose快速部署JupyterLab,包括拉取基础镜像,制作内置新插件的新镜像

基础镜像部署

基础镜像选择scipy-notebook:latest,内置了python3和一些基础的数据科学的库(选择它主要是内置的包比较常用)

1. 项目结构

先创建工作目录与挂载卷

mkdir ~/deploy/jupyterlab-docker/{notebooks,config}
cd ~/deploy/jupyterlab-docker

2. 核心配置文件

创建docker-compose.yaml

services:
  jupyterlab:
    image: jupyterlab:v4_mod  # 使用你的离线镜像(已含插件)
    # image: quay.io/jupyter/scipy-notebook:latest  # 基础镜像
    container_name: jupyterlab
    user: root
    ports:
      - "8888:8888"
    environment:
      - JUPYTER_TOKEN=${JUPYTER_TOKEN:-123456}
      - CHOWN_HOME=yes
      - CHOWN_HOME_OPTS=-R
      - GRANT_SUDO=yes
      # Python 包离线安装配置
      - PIP_NO_INDEX=1 # 外网pip直接安装时暂时注释
      - PIP_FIND_LINKS=/opt/packages
      - PIP_TARGET=/opt/python-packages
      - PYTHONPATH=/opt/python-packages:$PYTHONPATH
      - PATH=/opt/python-packages/bin:$PATH
      # 可选:主题配置持久化
      - JUPYTER_CONFIG_DIR=/home/jovyan/.jupyter-config
    volumes:
      # 代码工作区
      - ./notebooks:/home/jovyan/work
      # 离线 Python 包
      - ./packages:/opt/packages:ro
      # Python 包持久化(核心)
      - python_pkgs:/opt/python-packages
      # Jupyter 配置持久化(主题设置、快捷键等 - 可选但推荐)
      - jupyter_config:/home/jovyan/.jupyter-config
    working_dir: /home/jovyan/work
    entrypoint: >
      bash -c "
        mkdir -p /opt/python-packages /home/jovyan/.jupyter-config &&
        chown -R 1000:100 /opt/python-packages /home/jovyan/.jupyter-config &&
        echo '目录权限设置完成,正在启动 JupyterLab...' &&

        # 启动时自动安装 requirements.txt 中的包(如果还没装)
        if [ -f /opt/packages/requirements.txt ]; then
          echo '正在从离线包安装依赖...' && \
          /opt/conda/bin/python -m pip install --no-index --find-links=/opt/packages -r /opt/packages/requirements.txt || true
        fi && \
        
        # 使用 sudo 切换到 jovyan 用户运行(root 使用 sudo -u 不需要密码)
        exec sudo -E -u jovyan bash -c '
          export PATH=/opt/python-packages/bin:/opt/conda/bin:/usr/local/bin:/usr/bin:/bin
          export PYTHONPATH=/opt/python-packages
          export HOME=/home/jovyan
          cd /home/jovyan/work
          jupyter lab 
            --ip=0.0.0.0 
            --port=8888 
            --no-browser 
            --ServerApp.token=${JUPYTER_TOKEN} 
            --ServerApp.root_dir=/home/jovyan/work
        '
      "
    restart: unless-stopped

volumes:
  python_pkgs:
  jupyter_config:  # 保存你选择的黑色主题等个性化设置

创建.env文件

# ==========================================
# JupyterLab 基础配置
# ==========================================

# 访问密钥(Token):访问 http://localhost:8888 时输入这个密钥登录
JUPYTER_TOKEN=123456

# 登录密码哈希(可选):如果你想用"密码"代替"Token"登录
# JUPYTER_PASSWORD_HASH=sha1:xxxxxxxxx

# ==========================================
# 用户权限配置(与宿主机用户保持一致)
# ==========================================

# 宿主机用户ID(在终端运行 `id -u` 查看,通常是 1000)
PUID=1000
# 宿主机组ID(在终端运行 `id -g` 查看,通常是 1000)
PGID=1000

# Jupyter 容器内部使用的UID/GID(与PUID/PGID保持一致)
NB_UID=1000
NB_GID=1000

# ==========================================
# 可选配置
# ==========================================

# 时区设置(可选,默认UTC)
TZ=Asia/Shanghai

PYTHONPATH=

3. 快速启动

注意,v1版本的docker-compose命令是带横杠的,不带横杠的是v2版本

拉取镜像(如国内网络慢,先配置镜像源)

docker compose pull

启动服务

docker compose up -d

访问 http://<你的服务器IP>:8888,输入 token 或密码即可

内置新插件的镜像制作

可以先进到jupyterlab里面看看最新版的scipy-notebook镜像自带了哪些插件

!jupyter --version
Selected Jupyter core packages...
IPython          : 9.9.0
ipykernel        : 7.1.0
ipywidgets       : 8.1.8
jupyter_client   : 8.8.0
jupyter_core     : 5.9.1
jupyter_server   : 2.17.0
jupyterlab       : 4.5.3
nbclient         : 0.10.4
nbconvert        : 7.16.6
nbformat         : 5.10.4
notebook         : 7.5.2
qtconsole        : not installed
traitlets        : 5.14.3

!jupyter labextension list
JupyterLab v4.5.3
/opt/conda/share/jupyter/labextensions
        jupyterlab_pygments v0.3.0 enabled OK (python, jupyterlab_pygments)
        jupyter-matplotlib v0.12.0 enabled OK
        nbdime-jupyterlab v3.0.3 enabled OK
        @jupyter-notebook/lab-extension v7.5.2 enabled OK
        @jupyter-widgets/jupyterlab-manager v5.0.15 enabled OK (python, jupyterlab_widgets)
        @jupyterlab/git v0.51.4 enabled OK (python, jupyterlab-git)

可以看到有gitjupyter-matplotlib了,我们安装的时候不用重复安装,插件的话推荐支持pip直接安装的,可以直接预构建,不用配置npmnode环境

确定好你的plugin list的时候,这时候可以新建Dockerfile

FROM quay.io/jupyter/scipy-notebook:latest

USER root

# pip安装, _install plugins only - 预构建版本,无需 node/npm_
# 安装指定插件,合并RUN指令减少镜像层数
RUN pip install --no-cache-dir \
    # 单元格运行时间
    jupyterlab-execute-time \
    # 代码格式化(含black/isort依赖)
    jupyterlab-code-formatter black isort \
    # 黑色主题
    jupyterlab_materialdarker \
    # Excel预览
    jupyterlab-spreadsheet-editor \
    # 系统资源监控
    jupyter-resource-usage
    
# 如果是预构建插件(prebuilt),不需要 jupyter lab build
# 验证安装
RUN echo "=== 验证插件安装 ===" && \
    # 验证安装(双重验证:前端扩展+服务端扩展)
    jupyter labextension list && \
    jupyter server extension list && \
    # 确保安装的文件归属 jovyan 用户(避免权限问题)
    chown -R jovyan:users /opt/conda/lib/python3.*/site-packages && \
    chown -R jovyan:users /opt/conda/share/jupyter

# 预配置代码格式化器(创建默认配置)
RUN mkdir -p /etc/jupyter && \
    echo 'c.JupyterLabCodeFormatter.default_formatter = {"python": "black"}' >> /etc/jupyter/jupyter_config.py

USER jovyan

开始构建,新镜像的名字可以随便起。构建的时间可能稍微有点长…

docker build -t jupyterlab:mod .

查看我们构建的新镜像,可以看到体积也相应地增大了

william@DESKTOP:~/deploy/jupyterlab-docker$ docker images | grep -E "note|lab"
jupyterlab:v4_mod                           3ca87e52420f       4.83GB             0B   U    
quay.io/jupyter/scipy-notebook:latest       d3b700424ea9       3.64GB             0B 

内网部署

导出镜像

docker save jupyterlab:mod | gzip > jupyterlab_mod.tar.gz

通过FTP,或者U盘等介质,把镜像拷贝到内网机器,载入镜像

docker load -i jupyterlab_mod.tar.gz

安装额外的Python包

外网部署的jupyterlab想安装额外的包,直接在docker内的terminal或者jupyter里面的cell执行安装命令即可

pip install xxx

内网环境可以配置内网pip镜像源,此处省略

这里着重讲一下离线环境下安装python包,核心思路是:使用 PIP_TARGET 指定持久化目录 + 离线包挂载。相信在前面的docker-compose.yaml配置中你已经发现了端倪 ~

1. 外网准备离线包

在外网机器上执行(需要 Docker 环境):

mkdir -p jupyter_offline
cd jupyter_offline

# 创建 requirements.txt,列出你需要的包
cat > requirements.txt <<EOF
oracledb
EOF

# 下载所有依赖包(包括子依赖)到 packages 目录
mkdir -p packages
docker run --rm -v $(pwd)/packages:/packages -v $(pwd)/requirements.txt:/requirements.txt \
  quay.io/jupyter/scipy-notebook:latest \
  pip download -r /requirements.txt -d /packages

# 打包传输到内网
tar czf jupyter_offline.tar.gz packages requirements.txt
# 传到内网的 ~/jupyterlab-docker/ 目录下解压

2. 内网 Docker Compose 配置

和文章开头给的compose配置一致,这里只做解读,首先是环境变量

# 关键配置:离线安装 + 持久化目标
      - PIP_NO_INDEX=1                     # 强制离线模式(防止尝试连接外网)
      - PIP_FIND_LINKS=/opt/packages       # 从挂载目录找包
      - PIP_TARGET=/opt/python-packages    # 安装到这个目录(持久化)
      - PYTHONPATH=/opt/python-packages:$PYTHONPATH  # Python 能识别这些包
      - JUPYTER_PATH=/opt/python-packages/share/jupyter:$JUPYTER_PATH
      - PATH=/opt/python-packages/bin:$PATH

entrypoint执行命令,注意python需要使用绝对路径,避免docker内出现找不到命令的情况

mkdir -p /opt/python-packages /home/jovyan/.jupyter-config &&
chown -R 1000:100 /opt/python-packages /home/jovyan/.jupyter-config &&
echo '目录权限设置完成,正在启动 JupyterLab...' &&

# 启动时自动安装 requirements.txt 中的包(如果还没装)
if [ -f /opt/packages/requirements.txt ]; then
  echo '正在从离线包安装依赖...' && \
  /opt/conda/bin/python -m pip install --no-index --find-links=/opt/packages -r /opt/packages/requirements.txt || true
fi && \

3. 在内网使用(关键操作)

首次启动

# 把外网传进来的 packages 目录和 requirements.txt 放在 docker-compose.yml 同目录
docker compose up -d
# 查看日志确保离线包已自动安装
docker compose logs -f

后续在容器内手动安装新包(持久化)

在 JupyterLab 的 Terminal 中执行:

# 安装离线目录中的特定包
pip install /opt/packages/some-package.whl

# 或者如果你更新了 requirements.txt 并挂载了新的 packages:
pip install --no-index --find-links=/opt/packages requests

# 注意:因为 PIP_TARGET 和 PYTHONPATH 已设置,这些包会自动进入持久化目录 /opt/python-packages
# 即使 docker restart,这些包依然存在

验证持久化是否生效

# 在容器内安装一个新包,比如(假设你有这个离线包)
docker compose exec jupyterlab pip install --no-index --find-links=/opt/packages click

# 重启容器
docker compose restart

# 进入容器检查包是否还在
docker compose exec jupyterlab python -c "import click; print('持久化成功:', click.__file__)"
# 应该输出: /opt/python-packages/click/...

4. 进阶:添加新的离线包(迭代更新)

如果内网运行一段时间后,你需要增加新的包:

外网:

# 下载新包
docker run --rm -v $(pwd)/packages:/packages quay.io/jupyter/scipy-notebook:latest \
  pip download new-package-name -d /packages
# 重新 tar 传到内网,覆盖 packages 目录

内网:

# 替换 packages 目录后
docker compose exec jupyterlab pip install --no-index --find-links=/opt/packages new-package-name
# 无需重启,立即生效,且已持久化

方案优势说明

  1. 分离镜像与包:基础镜像保持干净,所有额外包放在独立持久化卷中
  2. 支持离线PIP_NO_INDEX=1 确保不会尝试连接外网,纯离线安装
  3. 重启不丢失:包安装在 python_pkgs 命名卷中,即使 docker rm 删除容器,卷还在
  4. 用户无感知:Jupyter 用户不需要知道背后的机制,正常使用 pip install 即可(受限于离线源),且安装的包自动持久化

注意:如果遇到权限问题(比如某些包需要编译),因为启动命令中有 chown -R jovyan:users /opt/python-packages,通常 jovyan 用户可以正常写入该目录。

更多推荐