一、问题和需求

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 指令

作用

当前示例说明

FROM

指定构建所基于的基础镜像

使用官方 python:3.10.18镜像作为起点

WORKDIR

设置容器内部的工作目录,后续指令将在此目录下执行

设置为 /myproject

COPY

将宿主机上的文件或目录复制到镜像中

先将 requirements.txt复制到镜像中

RUN

在构建过程中执行命令,常用于安装软件包、配置环境等

根据 requirements.txt安装 Python 依赖包

COPY

再次使用,复制项目所有文件

将当前目录所有文件复制到镜像的 /myproject 目录

EXPOSE

​声明​​容器运行时打算监听的网络端口

声明容器内应用使用 8001 端口

CMD

指定容器​​启动时​​执行的默认命令

(Dockerfile 中多个 CMD 仅最后一个生效)

容器启动时执行 python -m main

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 区域 (/var/lib/docker/volumes/)

​用户指定的任意宿主机路径​

​数据持久性​

​独立于容器​​,容器删除后卷和数据通常保留

数据保留,但需手动管理宿主机路径

​易用性​

无需指定宿主机路径,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. 启动时设置环境变量​

docker run -e TZ=Asia/Shanghai <你的镜像名>

最常用、灵活的方法,适用于临时或一次性的容器

​2. Dockerfile 配置​

在 Dockerfile 中添加:
ENV TZ=Asia/Shanghai

构建自定义镜像,希望镜像本身就有正确的时区设置

六、参考文章

如何将docker日志文件挂出来 | PingCode智库

Python项目打包并部署到Docker详细步骤_python_脚本之家

Dockerfile指令全解-CSDN博客

Docker中出现tag显示none镜像的处理方法_docker tag none-CSDN博客

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐