uv管理的python项目怎么打包成docker部署发布上线
uv管理的Python项目打包成docker报错,问了AI得到下面的结论。解决思路:把uv管理的python项目,导出成普通的 requirements.txt 文件,然后在容器里面按照pip安装包的项目来操作。
一、问题和需求
uv管理的Python项目打包成docker报错,问了AI得到下面的结论。
解决思路:把uv管理的python项目,导出成普通的 requirements.txt 文件,然后在容器里面按照pip安装包的项目来操作。
二、解决步骤
2.1 获取requirements.txt
在你的项目根目录下,打开终端,确保你已经激活了 uv 创建的虚拟环境(如果之前使用了的话),然后执行:
uv pip freeze > requirements.txt
这个命令会将当前虚拟环境中所有已安装的包及其精确版本导出到项目目录的 requirements.txt
文件中
2.2 构建dockerfile
比如我的uv 管理的python项目,正常启动项目是 先到~/myproject 这个工作目录,使用 uv run -m mian 命令直接启动。然后就可以启动对应的端口8001。这样的话,我们已经把这个项目变成了pip 管理的项目,就不用uv 了,按照正常的pip项目的话,启动命令应该改成python -m main
# 确定Python环境
FROM python:3.10-slim
# 设置容器内的绝对工作目录,替代您本地的 ~/myproject
WORKDIR /myproject
# 先将依赖文件复制到工作目录
COPY requirements.txt .
# 安装项目依赖包(使用国内镜像源可加速下载)
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 将当前目录下的所有项目文件复制到容器的 /myproject 目录
COPY . .
# 暴露应用运行的端口 (8001)
EXPOSE 8001
# 定义容器启动后在工作目录 /myproject 下执行的命令
CMD ["python", "-m", "main"]
这里使用官方 Python 精简版作为基础镜像会让镜像小很多。实测从1.8G变成0.8G。
Dockerfile 指令 |
作用 |
当前示例说明 |
---|---|---|
|
指定构建所基于的基础镜像 |
使用官方 |
|
设置容器内部的工作目录,后续指令将在此目录下执行 |
设置为 |
|
将宿主机上的文件或目录复制到镜像中 |
先将 |
|
在构建过程中执行命令,常用于安装软件包、配置环境等 |
根据 |
|
再次使用,复制项目所有文件 |
将当前目录所有文件复制到镜像的 |
|
声明容器运行时打算监听的网络端口 |
声明容器内应用使用 8001 端口 |
|
指定容器启动时执行的默认命令 (Dockerfile 中多个 CMD 仅最后一个生效) |
容器启动时执行 |
2.3 创建.dockerignore文件
在项目根目录创建 .dockerignore
文件,排除不必要的文件,能有效减小镜像体积,加速构建过程
常见的忽略文件如下:
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv
*.log
.git
.gitignore
.DS_Store
.env
2.4 构建 Docker 镜像
在包含 Dockerfile
的项目根目录下执行:
docker build -t myproject:2.0.0 .
-
-t
myproject:2.0.0为镜像指定名称和标签。 -
.
表示构建上下文是当前目录。
2.5 构建容器
docker run -d -p 8001:8001 --name my-bot myproject:2.0.0
# -d: 后台运行
# -p 8001:8001: 将宿主机的8001端口映射到容器的8001端口
# --name my-bot: 给容器起个名字
运行后,你可以通过 docker logs my-bot
查看容器日志,确认应用是否正常启动并在 8001 端口监听。
2.6 检查docker容器
docker ps -a
三、 遇到问题
3.1 容器占用
构建容器的时候遇到容器占用的问题
docker: Error response from daemon: Conflict. The container name "/my-bot" is already in use by container "52add4f9995f5a66d9ff2d7a415373a7a6dbd528347cbd5a9c6f5da5e8d49c97". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'.
# 强制删除名为 my-bot 的容器(包括正在运行的) 这样会造成虚挂镜像的问题
docker rm -f my-bot
# 删除后,再次尝试运行你的容器
docker run -d -p 8001:8001 --name my-bot myproject:2.0.0
3.2 出现 <none>
镜像
为什么出现 <none> 镜像?
当构建一个新的 Docker 镜像,并且指定的标签与本地已有的镜像重名时,Docker 会将旧的镜像的标签替换为 <none>,使得旧镜像成为悬挂镜像。
在 Docker 构建过程中,如果某一步失败,Docker 可能会留下一个空的镜像,这也是 <none> 镜像的一个来源。
如果您使用 docker save 保存镜像时没有指定镜像名称和标签,而是使用的镜像ID,那么加载(docker load)这个镜像后,它的 REPOSITORY 和 TAG 都会变成 <none>。
如果您强制删除了正在被容器使用的镜像,那么这个镜像也会变成 <none>。
您可以使用 docker images -f "dangling=true"
命令来查找所有的悬挂镜像。
docker images -f "dangling=true"
docker rmi $(docker images -f "dangling=true" -q)
3.3 删除镜像
列出镜像
docker images
# 删除单个镜像
docker rmi <镜像ID或名称>
# 示例:docker rmi d1e017099d5e# 删除多个镜像
docker rmi <镜像ID1> <镜像ID2> <镜像ID3>
如果镜像无法删除,大概率是镜像的示例容器正在使用
docker ps
docker inspect <容器ID或名称> # 可以查看容器的信息,由哪个镜像实例化的
docker stop <容器ID或名称>
docker rm <容器ID或名称>
3.4 容器间通讯
如果我们的项目A是docker部署,mysql也是docker部署。项目A需要连接到mysql容器,如何实现容器直接的通讯?
方法一:直接获取mysql容器的IP地址
使用下面的命令获取mysql容器的IP地址即可,注意,如果mysql容器重启的话,可能容器的IP地址会变化。
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <容器名称或ID>
方法二:使用自定义网络
要让容器A能访问MySQL容器,推荐使用自定义网络。这样容器间不仅可以通过容器名称通信(Docker内置了DNS解析),还能获得更好的隔离性和灵活性。
创建自定义网络
首先创建一个名为 my-network
的网络:
docker network create my-network
启动MySQL容器时使用 --network
参数将其连接到刚创建的网络。强烈建议通过环境变量设置root密码
docker run --name mysql-container \
-e MYSQL_ROOT_PASSWORD=your_password \ # 设置一个安全的密码
-e MYSQL_DATABASE=your_database \ # 可选:创建默认数据库
--network my-network \ # 连接到自定义网络
-d mysql:latest
启动你的项目容器(容器A)时,也将其加入到同一个网络 my-network
:
docker run --name container-A \
--network my-network \ # 连接到同一个自定义网络
-d your-project-image:tag
在容器A的应用配置中,使用MySQL容器的名称(mysql-container
)作为主机名来连接数据库
连接地址通常类似:
host: mysql-container # 注意这里用的是容器名称,而不是IP地址
port: 3306
database: your_database # 你之前环境变量里指定的数据库名
user: root # 或其他你创建的用户
password: your_password # 你设置的那个密码
使用 docker network inspect
命令可以查看某个特定网络(如你创建的 my-network
)中所有容器的IP地址信息
docker network inspect my-network
四、docker参数传输问题
4.1 需求
python项目需要连接redis,mysql等数据库,需要在实例化容器的时参数动态传入,从而实现构建一个镜像,可以构建不同的配置容器,实现一对多。而不是在构建镜像的时候就把参数硬编码了。
4.2 修改项目代码
假设db.py是存储数据库配置设置的文件。需要将参数设置成如下格式,让参数从环境变量中获取
import os
# 设置mysql的访问配置
username = os.getenv('MYSQL_USER','root') # 用户名
password = os.getenv('MYSQL_PASSWORD',"123456") # 密码
host = os.getenv('MYSQL_HOST',"0.0.0.0") # 主机IP
port = os.getenv('MYSQL_PORT',"3306") # mysql端口
database_name = os.getenv('MYSQL_DATABASE',"api_key.db") # 使用的数据库
4.3 修改dokerfile
将2.2的dockerfile进行修改如下:新增ENV参数
# 使用官方 Python 精简版作为基础镜像
FROM python:3.10-slim
# 设置容器内的绝对工作目录
WORKDIR /myproject
# 这些值会被 docker run -e 覆盖,或在 docker-compose.yml 中覆盖
ENV MYSQL_USER="root" \
MYSQL_PASSWORD="123456" \
MYSQL_HOST="0.0.0.0" \
MYSQL_PORT="3306" \
MYSQL_DATABASE="api_keys_db"
# 先将依赖文件复制到工作目录
COPY requirements.txt .
# 安装项目依赖包(使用国内镜像源可加速下载)
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 将当前目录下的所有项目文件复制到容器的工作目录
COPY . .
# 暴露应用运行的端口 (8001)
EXPOSE 8001
# 定义容器启动后执行的命令
CMD ["python", "-m", "main"]
4.4 构建镜像
进入项目的根目录
docker build -t myproject:2.0.0 .
-t myproject:2.0.0为镜像指定名称和标签。
.表示构建上下文是当前目录。
构建完成应该会显示如下的信息
Step 6/8 : COPY . .
---> bb8736645fae
Step 7/8 : EXPOSE 8001
---> Running in b1c353d139c5
---> Removed intermediate container b1c353d139c5
---> cb5b73efd495
Step 8/8 : CMD ["python", "-m", "main"]
---> Running in 8f417911321e
---> Removed intermediate container 8f417911321e
---> 1dde9a50891b
Successfully built 1dde9a50891b
Successfully tagged myproject:2.0.0
4.5 启动容器【不需要在项目目录下】
4.5.1 通过-e传入参数
执行docker run 的时候使用-e 可以替换每个容器对应的参数。
docker run -d \
--name myproject \
-p 8001:8001 \
-e MYSQL_HOST="1.1.1.1" \
-e MYSQL_USER="root" \
-e MYSQL_PASSWORD="456789" \
-e MYSQL_PORT="3306" \
-e MYSQL_DATABASE="test_db" \
myproject:2.0.0
4.5.2 通过.env文件传入参数
如果参数量比较多,就可以构建一个.env文件(文件名称可以随便取,一般取名为.env)
.env文件内容如下:注意不要用""括起来,直接写变量名和变量值即可。
MYSQL_HOST=1.1.1.1
MYSQL_USER=root
MYSQL_PASSWORD=456789
MYSQL_PORT=3306
MYSQL_DATABASE=test_db
然后再通过 --env-file 参数把文件的路径写进去启动容器。
docker run -d --name myproject --env-file \path\.env -p 8001:8001 myproject:2.0.0
4.6 运行中的容器执行指定脚本
4.6.1 需求和问题
项目中已经写好了创建表的脚本和获取API_KEY的脚本。如果正常执行的命令是 python -m configs.creat_db,我们如何在容器启动后还能脚本。
4.6.2 docker exec
docker exec <容器名称或ID> 脚本命令
例如使用容器执行脚本,就可以在数据库中创建表。
docker exec a9c3790e679b python -m configs.creat_db
五、docker容器的日志怎么挂出来
5.1 问题和需求
项目正常打包成docker发布后,假设自己的项目,有个log目录是专门存放项目运行的日志信息的,按天存储日志文件比如:20250919.log,20250920.log ...那么这个容器的日志怎么挂载在宿主机上进行操作。
5.2 docker管理卷和绑定挂载对比
特性 |
Docker 管理卷 (Volume) |
绑定挂载 (Bind Mount) |
---|---|---|
管理方式 |
由 Docker 引擎创建和管理 |
使用用户自行管理的宿主机目录或文件 |
存储位置 |
Docker 区域 ( |
用户指定的任意宿主机路径 |
数据持久性 |
独立于容器,容器删除后卷和数据通常保留 |
数据保留,但需手动管理宿主机路径 |
易用性 |
无需指定宿主机路径,Docker自动管理 |
需指定宿主机绝对路径,需确保路径存在和权限正确 |
可移植性 |
好,与宿主机路径无关 |
差,依赖特定宿主机路径 |
适用场景 |
生产环境、持久化数据、多容器共享数据 |
开发环境、需直接修改宿主机文件或代码、配置映射 |
docker卷,使用 docker rm <容器名称或ID> 命令来删除容器的话,docker卷信息还会持久化保存,只有当使用docker rm -v <容器名称或ID>命令来删除的话,docker会删除掉和这个容器管理的docker卷。
5.3 使用 Docker 管理卷 (Volume)
5.3.1 创建命名卷
docker volume create myproject_logs
可以使用下面的命令查看卷的具体信息
docker volume inspect myproject_logs
删除docker卷
docker volume rm myproject_logs
删除卷是不可逆操作,卷内的所有数据都会被永久删除.
保持良好的习惯,在删除不再使用的容器时使用 docker rm -v
命令,以便 Docker 同时清理其关联的匿名卷,避免积攒过多无用卷
5.3.2 修改你的运行命令,添加卷挂载
docker run -d \
--name myproject \
-v myproject_logs:/myproject/logs \
-p 8001:8001 \
-e MYSQL_HOST="1.1.1.1" \
-e MYSQL_USER="root" \
-e MYSQL_PASSWORD="456789" \
-e MYSQL_PORT="3306" \
-e MYSQL_DATABASE="test_db" \
myproject:2.0.0
-v myproject_logs:/myproject/logs 表示把宿主机上的名叫myproject_logs的docker卷映射到容器的
/myproject/logs目录下。
如果容器内的 /myproject/logs目录不存在,Docker 会自动创建它
这样运行容器的时候,就可以在宿主机上的docker卷上查看容器的日志信息了。
5.4 容器的日志时间问题
问题,项目在容器运行后,日志的时间和北京时间不一样。原因是Docker 容器默认使用 UTC 时区(世界协调时间),而你的宿主机很可能使用的是 本地时区(例如北京时间 CST,即 UTC+8)。这会导致容器内应用程序记录日志时,如果直接使用系统时间,就会产生 8 小时的时间差。
解决办法:
方法 |
命令或配置示例 |
适用场景 |
---|---|---|
1. 启动时设置环境变量 |
|
最常用、灵活的方法,适用于临时或一次性的容器 。 |
2. Dockerfile 配置 |
在 Dockerfile 中添加: |
构建自定义镜像,希望镜像本身就有正确的时区设置 |
六、参考文章
更多推荐
所有评论(0)