Docker 已经成为现代后端部署的标配工具。本文将带你用 20 分钟,从零开始把一个 Python Web 服务容器化并运行起来。

为什么要用 Docker

在没有 Docker 的时代,部署一个服务需要手动安装运行时、配置依赖、处理环境差异。“在我机器上能跑"成了经典甩锅语录。

Docker 把应用和它的运行环境打包成一个镜像(Image),在任何装了 Docker 的机器上都能以相同方式运行。核心思路很简单:用代码定义环境,而不是手动配置

几个关键概念:

  • 镜像(Image):只读的模板,包含应用代码和运行所需的一切
  • 容器(Container):镜像的运行实例,可以启动、停止、删除
  • Dockerfile:构建镜像的配方文件
  • docker-compose.yml:定义多个容器如何协同工作的配置文件

准备工作

确保机器上已安装 Docker 和 Docker Compose:

1
2
docker --version
docker compose version

如果没装,参考 Docker 官方文档。Linux 用户装完后把当前用户加入 docker 组,免得每次都加 sudo

1
sudo usermod -aG docker $USER

重新登录终端生效。

第一步:写一个简单的 Web 服务

创建一个项目目录:

1
mkdir docker-demo && cd docker-demo

写一个最小的 Flask 应用 app.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return {"message": "Hello from Docker!"}

@app.route("/health")
def health():
    return {"status": "ok"}

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

再写一个 requirements.txt

flask==3.1.*

第二步:编写 Dockerfile

在项目根目录创建 Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

逐行解释:

指令作用
FROM python:3.12-slim基础镜像,slim 版本体积小
WORKDIR /app设置容器内的工作目录
COPY requirements.txt .先复制依赖文件,利用 Docker 层缓存
RUN pip install ...安装依赖
COPY . .再复制应用代码
EXPOSE 5000声明端口(文档作用,不实际映射)
CMD ["python", "app.py"]容器启动时运行的命令

为什么分两步 COPY? Docker 会缓存每一层。如果代码改了但 requirements.txt 没变,pip install 那层会直接走缓存,构建速度更快。

第三步:编写 docker-compose.yml

1
2
3
4
5
6
services:
  app:
    build: .
    ports:
      - "5000:5000"
    restart: unless-stopped

这比直接用 docker run 更清晰,而且一条命令就能管理。

第四步:构建并运行

1
docker compose up --build

第一次会下载基础镜像并安装依赖,之后再构建会很快。看到 Flask 的启动日志后,打开另一个终端测试:

1
2
curl http://localhost:5000
# {"message":"Hello from Docker!"}

Ctrl+C 停止。如果想后台运行:

1
2
3
docker compose up -d      # 后台启动
docker compose logs -f     # 查看日志
docker compose down        # 停止并移除容器

第五步:加入数据库

实际项目通常需要数据库。给 docker-compose.yml 加一个 PostgreSQL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
services:
  app:
    build: .
    ports:
      - "5000:5000"
    environment:
      DATABASE_URL: postgresql://postgres:secret@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  pgdata:

几个要点:

  • depends_on + service_healthy 确保数据库就绪后才启动应用
  • volumes 让数据库数据持久化,容器删了数据还在
  • 同一个 compose 文件里的服务可以用服务名db)作为主机名互相访问

常见问题

容器内访问宿主机服务

host.docker.internal(macOS/Windows)或 host.network(Linux)。

镜像太大

slimalpine 基础镜像;多阶段构建把构建工具和运行时分开。

修改代码后不生效

挂载本地目录到容器内,开发时热更新:

1
2
3
4
5
6
7
services:
  app:
    build: .
    volumes:
      - .:/app
    ports:
      - "5000:5000"

如何查看运行中的容器

1
2
3
docker ps              # 查看运行中的容器
docker compose ps      # 查看当前项目的容器
docker exec -it <容器名> bash   # 进入容器内部

生产环境建议

这篇文章覆盖的是开发环境的基本用法。上生产前还需要考虑:

  • 不要硬编码密码:用 .env 文件或密钥管理服务
  • 网络隔离:数据库端口不要映射到宿主机,只让应用容器访问
  • 日志管理:配置日志驱动,避免容器日志撑爆磁盘
  • 资源限制:设置 CPU 和内存上限,防止单个容器耗尽资源

小结

整个流程就是四步:写代码 → 写 Dockerfile → 写 docker-compose.yml → docker compose up。Docker 的核心价值不在于技术有多复杂,而在于它把"部署"从一件靠记忆和运气的事,变成了一件可以版本控制、可以复现、可以自动化的事。