终极版 Python 项目 Docker 化教程 (小白友好)

你好!欢迎来到这份保姆级教程。我们将一起把你电脑上的 Python 项目,通过 uvDocker 这两个现代化工具,打包成一个标准、可移植的“魔法盒子”。这个盒子可以在任何安装了 Docker 的电脑或服务器上运行,完全不用担心环境配置问题。

我们的蓝图:项目结构

首先,我们确认一下你的项目,这是我们所有工作的起点:

/你的项目文件夹 (例如 my-cool-app)
|
|-- data/             # 存放重要数据的文件夹,需要永久保留
|-- core/             # 存放其他 Python 代码的文件夹
|   `-- __init__.py   # (最好有这个文件)
|   `-- utils.py      # (你的其他代码文件)
|
`-- app.py            # 启动程序的主文件

第零步:准备工具 (在你自己的电脑上)

在开始施工前,我们需要准备好我们的“瑞士军刀”uv

  1. 安装 uv:
    打开你的终端(Windows 上的 PowerShell 或 CMD,Mac/Linux 上的 Terminal),运行:

    pip install uv
    
  2. 为项目创建“沙盒” (虚拟环境):
    每个项目都应该有自己独立的环境,防止依赖冲突。

    # 1. 进入你的项目文件夹
    cd path/to/my-cool-app  # 把这里换成你项目的真实路径
    
    # 2. 创建一个名为 .venv 的沙盒
    uv venv
    
  3. 进入“沙盒”:

    • 在 Windows 上: .\.venv\Scripts\activate
    • 在 Mac 或 Linux 上: source .venv\bin\activate
      激活后,你会看到终端提示符前多了个 (.venv)
  4. 创建项目“身份证” (pyproject.toml):
    我们需要一个文件来记录项目的基本信息。

    uv init
    

    它会问几个问题,一路按回车键使用默认设置即可。

第一步:添加项目“配料” (Python 依赖包)

假设你的 app.py 需要用到 Flask 包。我们用 uv 把它加进去。

uv add flask
```uv` 会自动下载 `flask` 并把它记录到 `pyproject.toml` 文件中。如果还需要其他包,用同样的方法添加即可。

---

### 第二步:生成精确的“配料清单” (锁定依赖)

为了保证在任何地方构建出来的环境都一模一样,我们需要一张精确到版本号的“配料清单”。

```bash
uv pip compile pyproject.toml -o requirements.lock

这个命令会生成一个 requirements.lock 文件。你不需要手动编辑它,它是给 Docker 看的。

第三步:编写施工“菜谱” (Dockerfile)

Dockerfile 告诉 Docker 如何一步步地构建我们项目的“魔法盒子”(也就是 Docker 镜像)。在你的项目根目录创建一个名为 Dockerfile 的文件,把下面的内容复制进去:

# Dockerfile

# 1. 选一个带 Python 的“毛坯房”
FROM python:3.11-slim

# 2. 在“毛坯房”里安装我们的施工工具 uv
RUN pip install --no-cache-dir uv

# 3. 在房子里创建一个叫 /app 的工作区
WORKDIR /app

# 4. 把“精确配料清单”和“身份证”先搬进去
COPY pyproject.toml requirements.lock* ./

# 5. 让施工队按照清单,把所有“配料”(Python包)准备好
RUN uv pip sync --system requirements.lock

# 6. 把我们项目的所有代码都搬进去 (详细解释见下方!)
COPY . .

# 7. 告诉外界,这个盒子里的服务在 5000 端口
EXPOSE 5000

# 8. 最后,定义打开盒子后,默认要执行的命令:启动 app.py
CMD ["python", "app.py"]
【深度解析】COPY 命令:一次性的“搬家公司”

COPY 指令在构建镜像时执行,它的作用是把你的文件从电脑复制到镜像内部。可以把它想象成一个搬家公司,一次性地把你的家具(代码文件)搬进新家(镜像)。

它的基本语法是:COPY <源路径> <目标路径>

  • <源路径>:指你电脑上的文件或文件夹。这个路径是相对于你运行 docker-compose up 命令的文件夹(通常是项目根目录)。
  • <目标路径>:指镜像内部的路径。如果使用相对路径(比如 ../core/),那么它是相对于 WORKDIR(我们这里是 /app)而言的。

COPY . . 的意思是:

项目根目录(第一个.)下的所有内容,复制到镜像内工作区 /app(第二个.)下。

如何精细化调整 COPY

方法一 (最佳实践): 使用 .dockerignore 文件

这是最推荐的方式。在项目根目录创建一个 .dockerignore 文件,告诉“搬家公司”哪些东西不要搬

# .dockerignore - 搬家黑名单

# 忽略 Python 的虚拟环境,它非常大且不需要
.venv

# 忽略 Python 运行产生的缓存
__pycache__/
*.pyc

# 忽略 Git 的版本控制文件
.git
.gitignore

# 忽略 Docker 自己,避免把自己也打包进去
.dockerignore
Dockerfile
docker-compose.yml

有了这个文件,你就可以放心地使用 COPY . .,Docker 会自动忽略黑名单上的所有内容,非常省心。

方法二 (手动精确控制): 只复制特定的文件和文件夹

如果你想手动指定每一个要搬运的文件,可以这样做。这能让你对镜像内容有百分之百的控制。

假设你想只复制 app.pycore 文件夹,可以这样修改 Dockerfile 的第6步:

# ... (之前的步骤) ...

# 第6步的替代方案:
# 1. 把电脑上的 app.py 复制到镜像里的 /app/ 文件夹下
COPY app.py .

# 2. 把电脑上的 core/ 文件夹整体复制到镜像里的 /app/ 文件夹下,并命名为 core
COPY core/ ./core/

# ... (之后的步骤) ...
  • COPY app.py . -> <源>app.py文件,<目标>是工作区/app
  • COPY core/ ./core/ -> <源>core文件夹,<目标>是工作区/app下的core文件夹。

第四步:编写“总指挥” (docker-compose.yml)

docker-compose.yml 负责启动和管理我们的“魔法盒子”,包括设置网络、连接数据等。在项目根目录创建 docker-compose.yml 文件:

# docker-compose.yml

version: '3.8'

services:
  # 'webapp:' 是我们给这个服务起的“代号”,详细解释见下方
  webapp:
    build: .
    ports:
      - "8080:5000"
    volumes:
      # 这是关键!用来处理你的 data 目录 (详细解释见下方!)
      - ./data:/app/data
    restart: always
【深度解析】服务名(webapp) vs 容器名(container_name)

你可能已经注意到 webapp: 这一行。它是什么意思?它和 container_name 又有什么区别?

  • 服务名 (webapp:):

    • 是什么? 这是你在 docker-compose.yml 文件中为一组容器(服务)定义的内部代号/昵称
    • 作用? 主要用于服务间通信。如果你的项目还有一个数据库服务(比如叫 db:),你的 webapp 服务就可以通过 http://webapp:5000ping webapp 这样的地址来访问它。它也是你在命令行中管理这个服务的标识,例如 docker-compose logs -f webapp
  • 容器名 (container_name):

    • 是什么? 这是一个可选参数,用于给 Docker 创建的单个容器指定一个全局唯一的“大名”
    • 作用? 当你运行 docker ps 时,显示的就是这个名字。如果你不设置它,Docker Compose 会自动生成一个名字,比如 my-cool-app_webapp_1

核心区别与最佳实践

特性 服务名 (webapp) container_name
性质 内部昵称 (在 Compose 项目内) 全局大名 (在整个 Docker 中)
唯一性 docker-compose.yml 文件内唯一 在整个电脑的 Docker 环境中必须唯一
扩展性 支持。可轻松启动多个实例。 不支持。固定名字后无法水平扩展。
推荐用法 总是使用。这是定义服务的基础。 几乎从不使用。除非有特殊外部脚本需要固定名字。

结论:对于99%的情况,只使用服务名,不要画蛇添足地设置 container_name

# 一个不推荐的例子
services:
  webapp:
    build: .
    container_name: my-cool-app-container # 不推荐!这会让你无法启动多个webapp实例
    ports:
      - "8080:5000"
【深度解析】volumes vs COPY:神奇“任意门”与“搬家公司”的区别

这是 Docker 中最核心也最容易混淆的概念。理解了它,你就真正入门了。

特性 COPY (搬家公司) volumes (任意门)
发生时间 盖房子时 (构建镜像时) 入住后 (启动容器时)
数据关系 一次性复制。数据成为房子的一部分,是静态的。 实时链接/同步。数据仍然在房子外部(你的电脑上),是动态的。
数据持久性 如果房子拆了(容器被删除),入住后产生的新变化会丢失 房子拆了,数据毫发无损,因为它一直在你的电脑上。
核心用途 用于将应用程序本身的代码、配置等静态文件构建到镜像中。 用于需要永久保存的动态数据,如用户上传的文件、数据库文件、日志等。

volumes: - ./data:/app/data 这行配置的含义是:

在你的电脑./data)和运行中的容器/app/data)之间,建立一个实时同步的**“任意门”**。

这意味着:

  • 你在电脑上修改 data 文件夹,容器里立刻同步。
  • 你的程序在容器里往 /app/data 写数据,你电脑上的 data 文件夹也立刻能看到。
  • 这完美地解决了你需要保留 data 目录的需求!
【重要补充】如果文件在没有“任意门”的地方产生会怎样?

这是一个非常好的问题!如果你的代码在没有通过 volumes 挂载的目录(比如直接在 /app 根目录)下生成了文件,会发生什么?

  • 会生成吗?会报错吗?
    文件会成功生成,代码不会报错。 因为容器内部是一个完整的、可写的文件系统,你的程序有权限在它的工作目录 (/app) 里创建文件。从程序的角度看,一切正常。

  • 我能从电脑上访问到这个文件吗?
    不能。 这个新生成的文件被“困”在了容器这个“一次性盒子”的内部文件系统中。因为没有为它设置“任意门”(volume),所以你的电脑对此一无所知。

  • 最终的后果是什么?
    文件会永久丢失。 当你运行 docker-compose down 时,容器这个“一次性盒子”会被彻底销毁。所有没有通过“任意门”连接到外面的东西,包括这个新生成的文件,都会随之永久消失

  • 结论(最佳实践):
    这就是为什么我们必须养成一个好习惯:将所有动态数据的读写(比如日志、用户上传、计算结果)都严格指向一个已经通过 volumes 挂载的目录(如本教程中的 /app/data 或自己指定的 /app/output 等)。代码与动态数据分离是 Docker 数据持久化的核心原则。

第五步:启动!

现在,一个命令启动所有!
在终端(确保在项目根目录下)运行:

docker-compose up --build -d
  • --build: 首次运行或修改了 Dockerfile 后,需要加上它来构建镜像。
  • -d: 在后台运行。

如何验证?

  1. 运行 docker-compose ps,如果看到 webapp 服务的状态是 running,就成功了第一步。
  2. 为了测试,可以修改 app.py
    # app.py
    from flask import Flask
    import os
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello():
        data_path = '/app/data'
        files_in_data = "文件夹不存在或为空"
        if os.path.exists(data_path):
            try:
                files_in_data = ", ".join(os.listdir(data_path))
                if not files_in_data:
                    files_in_data = "文件夹为空"
            except Exception as e:
                files_in_data = f"读取错误: {e}"
    
        return f"你好, Docker! 'data' 文件夹里的文件有: [ {files_in_data} ]"
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5000)
    
  3. 在你的 data 文件夹里随便放个文件,比如 test.txt
  4. 然后打开浏览器,访问 http://localhost:8080。如果你能看到页面显示了 test.txt,那就大功告成!

第六步:进阶日常管理 (常用命令大全)

掌握以下命令,你就可以像专业人士一样管理你的 Docker 应用了。

1. 查看状态
  • 查看所有服务状态

    docker-compose ps
    

    这是你的“仪表盘”,会列出所有服务(比如 webapp),告诉你它们是否在运行 (running)、已退出 (exited),以及端口映射情况。

  • 查看实时日志

    docker-compose logs -f webapp
    

    想知道你的 app.py 里的 print 信息都去哪了?就在这里!-f 参数表示持续跟踪,新的日志会实时刷新出来。要查看其他服务,把 webapp 换成对应的服务名即可。按 Ctrl+C 退出。

2. 启动与停止
  • 启动所有服务

    docker-compose up -d
    

    这是最常用的启动命令。-d 表示在后台运行。

  • 停止所有服务 (温柔模式)

    docker-compose stop
    

    这会暂停所有正在运行的容器,但不会删除它们。下次你可以用 docker-compose start 快速恢复。

  • 启动已停止的服务

    docker-compose start
    

    用于启动被 stop 命令暂停的服务,速度很快,因为它不需要重新创建容器。

  • 停止并移除容器 (常用)

    docker-compose down
    

    这是最常用的停止命令。它会停止并移除容器和网络。但别担心,你通过 volumes 连接的 data 文件夹不会受影响。

  • 彻底清除 (危险!)

    docker-compose down -v
    

    这个命令除了会做 down 的所有事情,还会删除docker-compose.yml 中定义的 volumes(比如数据库的数据卷)。我们的 data 目录因为是直接链接的,所以不受影响。但如果你用了命名卷来存数据库,这个命令会把数据清空!请谨慎使用,通常只在你想彻底重置项目时才用。

3. 更新与维护
  • 修改代码后,重建并启动

    docker-compose up --build -d
    

    当你修改了 app.pycore 里的代码,或者改了 Dockerfile,就需要用这个命令。--build 会强制 Docker 重新构建你的镜像,应用所有代码改动。

  • 进入正在运行的容器内部

    docker-compose exec webapp bash
    

    这是一个超级有用的“黑客”命令!它能让你在正在运行的 webapp 容器里打开一个终端 (bash)。进去之后,你就可以像在普通 Linux 系统里一样,用 ls, cd, cat 等命令查看容器内部的文件结构,或者手动运行一些 Python 脚本来调试。输入 exit 退出。

  • 更新基础镜像

    docker-compose pull
    

    这个命令会去检查你的 Dockerfile 中使用的基础镜像(比如 FROM python:3.11-slim)是否有更新的版本,并把它下载下来。之后再运行 docker-compose up --build 就会使用最新的基础镜像来构建你的应用了。这有助于保持安全性。

恭喜你!你已经完整地掌握了使用现代工具将 Python 项目 Docker 化的全过程。这个流程不仅高效,而且非常规范,可以轻松应用到你未来的任何项目中。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐