Python FastAPI 项目使用 Docker 部署并映射端口(完整教程)

文章目录

    • 前言
    • 一、最终部署架构
    • [二、创建 FastAPI 项目](#二、创建 FastAPI 项目)
    • 三、配置项目依赖
    • [四、编写 Dockerfile](#四、编写 Dockerfile)
      • [1. 基础镜像](#1. 基础镜像)
      • [2. 工作目录](#2. 工作目录)
      • [3. 安装 Python 依赖](#3. 安装 Python 依赖)
      • [4. 声明容器端口](#4. 声明容器端口)
      • [5. 启动 FastAPI](#5. 启动 FastAPI)
    • [五、配置 Docker Compose](#五、配置 Docker Compose)
    • [六、为什么使用 127.0.0.1](#六、为什么使用 127.0.0.1)
    • 七、配置环境变量
    • [八、配置 Docker 忽略文件](#八、配置 Docker 忽略文件)
    • [九、构建 Docker 镜像](#九、构建 Docker 镜像)
    • 十、启动容器
    • 十一、验证接口
      • [1. 查看端口映射](#1. 查看端口映射)
      • [2. 测试健康检查](#2. 测试健康检查)
      • [3. 访问 Swagger](#3. 访问 Swagger)
      • [4. 测试业务接口](#4. 测试业务接口)
    • 十二、修改宿主机映射端口
    • [十三、配置 Nginx 反向代理](#十三、配置 Nginx 反向代理)
    • [十四、配置 HTTPS](#十四、配置 HTTPS)
    • [十五、不使用 Docker Compose 的写法](#十五、不使用 Docker Compose 的写法)
  • 踩坑记录

前言

最近需要把一个 Python 接口部署到服务器,并提供给业务方调用。

项目使用 FastAPI 开发,部署时主要有下面几个需求:

  1. 使用 Docker 打包并运行 FastAPI。
  2. 将容器里的 8000 端口映射到宿主机的 18000 端口。
  3. FastAPI 端口不直接暴露到公网。
  4. 使用 Nginx 对外提供访问。
  5. 后续可以继续配置域名和 HTTPS。

部署过程看起来不复杂,但实际操作时可能遇到以下问题:

  • Docker Hub连接超时;
  • python:3.12-slim 镜像拉取失败;
  • pip install 十几分钟没有反应;
  • Dockerfile 提示 unknown instruction;
  • 不清楚宿主机端口和容器端口的区别;
  • 修改端口后没有生效。

本文记录一套完整的部署过程,并把这些常见问题集中整理出来。


一、最终部署架构

部署完成后的访问关系如下:

text 复制代码
业务方
  ↓
HTTPS 443
  ↓
Nginx
  ↓
http://127.0.0.1:18000
  ↓
Docker端口映射
  ↓
FastAPI容器 8000

本次使用的端口:

服务位置 端口
FastAPI 容器内部 8000
Docker 宿主机 18000
Nginx 对外端口 80/443

端口映射关系:

text 复制代码
宿主机 127.0.0.1:18000 → Docker 容器 8000

二、创建 FastAPI 项目

项目目录如下:

text 复制代码
order-api/
├── app/
│   ├── __init__.py
│   └── main.py
├── .dockerignore
├── .env
├── Dockerfile
├── compose.yaml
└── requirements.txt

创建 app/main.py:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(
    title="订单服务",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
)


class OrderRequest(BaseModel):
    customer_id: str
    amount: float


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


@app.get("/ready")
def ready():
    return {
        "status": "ready"
    }


@app.post("/api/v1/orders")
def create_order(request: OrderRequest):
    return {
        "code": "0",
        "message": "success",
        "data": {
            "customer_id": request.customer_id,
            "amount": request.amount,
        },
    }

这里一共提供了三个接口:

text 复制代码
GET  /health
GET  /ready
POST /api/v1/orders

其中 /health 用于检查服务是否正常运行。

FastAPI 还会自动生成 Swagger 文档:

text 复制代码
http://服务地址/docs

三、配置项目依赖

创建 requirements.txt:

text 复制代码
fastapi[standard]

如果项目中使用了数据库、Redis 等组件,可以继续添加:

text 复制代码
sqlalchemy
pymysql
redis
alembic

生产环境建议固定依赖版本,避免某个依赖发布新版本后导致镜像重新构建失败。

例如:

text 复制代码
fastapi[standard]==经过测试的版本

四、编写 Dockerfile

创建 Dockerfile:

dockerfile 复制代码
FROM public.ecr.aws/docker/library/python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /code

COPY requirements.txt /code/requirements.txt

ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple

RUN python -m pip install \
    --no-cache-dir \
    --disable-pip-version-check \
    --timeout 60 \
    --retries 3 \
    --index-url "${PIP_INDEX_URL}" \
    -r /code/requirements.txt

COPY app /code/app

RUN useradd --create-home --uid 10001 appuser

USER appuser

EXPOSE 8000

CMD ["fastapi", "run", "app/main.py", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips=*"]

下面简单说明几个关键配置。

1. 基础镜像

dockerfile 复制代码
FROM public.ecr.aws/docker/library/python:3.12-slim

正常情况下可以直接使用:

dockerfile 复制代码
FROM python:3.12-slim

但是如果当前网络访问 Docker Hub 超时,可以尝试 AWS 公共镜像仓库中的镜像:

dockerfile 复制代码
FROM public.ecr.aws/docker/library/python:3.12-slim

2. 工作目录

dockerfile 复制代码
WORKDIR /code

后续复制的代码和执行的命令都以 /code 为工作目录。

3. 安装 Python 依赖

dockerfile 复制代码
RUN python -m pip install \
    --no-cache-dir \
    --disable-pip-version-check \
    --timeout 60 \
    --retries 3 \
    --index-url "${PIP_INDEX_URL}" \
    -r /code/requirements.txt

如果默认 PyPI 下载速度较慢,可以通过 PIP_INDEX_URL 指定一个可访问的镜像。

公司内部如果有 Python 制品仓库,优先使用公司仓库。

4. 声明容器端口

dockerfile 复制代码
EXPOSE 8000

需要注意:

EXPOSE 8000 只说明程序在容器中使用 8000 端口,并不会自动把该端口映射到宿主机。

真正的端口映射需要在 Docker Compose 中配置。

5. 启动 FastAPI

dockerfile 复制代码
CMD ["fastapi", "run", "app/main.py", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips=*"]

建议将 CMD 写成完整的一行。

如果直接拆成下面这种格式:

dockerfile 复制代码
CMD [
  "fastapi",
  "run"
]

Dockerfile 没有正确添加续行符时,可能会报错:

text 复制代码
unknown instruction: "fastapi"

五、配置 Docker Compose

创建 compose.yaml:

yaml 复制代码
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        PIP_INDEX_URL: https://pypi.tuna.tsinghua.edu.cn/simple

    image: order-api:1.0.0
    container_name: order-api

    restart: unless-stopped

    env_file:
      - .env

    ports:
      - "127.0.0.1:18000:8000"

    healthcheck:
      test:
        [
          "CMD",
          "python",
          "-c",
          "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=5)"
        ]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

这里最重要的是下面这段:

yaml 复制代码
ports:
  - "127.0.0.1:18000:8000"

Docker 端口映射格式:

text 复制代码
宿主机IP:宿主机端口:容器端口

因此,这条配置的含义是:

text 复制代码
127.0.0.1 : 18000 : 8000
    ↓          ↓       ↓
宿主机IP   宿主机端口  容器端口

最终访问关系:

text 复制代码
http://127.0.0.1:18000
             ↓
Docker容器中的8000端口

六、为什么使用 127.0.0.1

本文使用:

yaml 复制代码
ports:
  - "127.0.0.1:18000:8000"

这表示 18000 端口只允许宿主机本机访问。

局域网和公网中的其他电脑不能直接访问:

text 复制代码
http://服务器IP:18000

这种方式适合生产环境,因为 FastAPI 不会直接暴露到公网,外部请求统一经过 Nginx。

如果确实需要让其他电脑直接访问,可以修改为:

yaml 复制代码
ports:
  - "18000:8000"

它等价于:

yaml 复制代码
ports:
  - "0.0.0.0:18000:8000"

其他电脑便可以访问:

text 复制代码
http://宿主机IP:18000

但正式环境不建议这样配置。


七、配置环境变量

创建 .env:

dotenv 复制代码
APP_ENV=production

如果项目中使用数据库和 Redis,可以继续添加:

dotenv 复制代码
DATABASE_URL=mysql+pymysql://user:password@database-host:3306/order_db
REDIS_URL=redis://redis-host:6379/0
API_TOKEN=replace-with-real-token

不要将 .env 提交到 Git。

建议在 .gitignore 中添加:

gitignore 复制代码
.env
.venv/
__pycache__/
*.pyc
.pytest_cache/

八、配置 Docker 忽略文件

创建 .dockerignore:

text 复制代码
.git
.env
.venv
__pycache__
.pytest_cache
*.pyc
tests

这样可以避免无关文件和敏感信息进入 Docker 镜像。


九、构建 Docker 镜像

执行:

bash 复制代码
docker compose build --progress=plain

--progress=plain 可以显示完整的构建日志。

如果需要忽略缓存,重新构建:

bash 复制代码
docker compose build --no-cache --progress=plain

如果构建成功,可以查看镜像:

bash 复制代码
docker images

预期可以看到:

text 复制代码
order-api   1.0.0

十、启动容器

执行:

bash 复制代码
docker compose up -d

如果修改过 Dockerfile 或端口映射,建议执行:

bash 复制代码
docker compose up -d --build --force-recreate

查看运行状态:

bash 复制代码
docker compose ps

正常情况下会看到:

text 复制代码
NAME        STATUS          PORTS
order-api   Up (healthy)    127.0.0.1:18000->8000/tcp

其中:

text 复制代码
127.0.0.1:18000->8000/tcp

说明宿主机的 18000 已经成功映射到容器的 8000。


十一、验证接口

1. 查看端口映射

bash 复制代码
docker port order-api

预期:

text 复制代码
8000/tcp -> 127.0.0.1:18000

2. 测试健康检查

bash 复制代码
curl http://127.0.0.1:18000/health

预期:

json 复制代码
{"status":"ok"}

3. 访问 Swagger

浏览器打开:

text 复制代码
http://127.0.0.1:18000/docs

4. 测试业务接口

bash 复制代码
curl -X POST 'http://127.0.0.1:18000/api/v1/orders' \
  -H 'Content-Type: application/json' \
  -d '{
    "customer_id": "C10001",
    "amount": 99.5
  }'

预期:

json 复制代码
{
  "code": "0",
  "message": "success",
  "data": {
    "customer_id": "C10001",
    "amount": 99.5
  }
}

十二、修改宿主机映射端口

假设需要将宿主机端口从 18000 改成 9000:

yaml 复制代码
ports:
  - "127.0.0.1:9000:8000"

重新创建容器:

bash 复制代码
docker compose up -d --force-recreate

新访问地址:

bash 复制代码
curl http://127.0.0.1:9000/health

这里修改的是宿主机端口,容器中的 FastAPI 仍然使用 8000。


十三、配置 Nginx 反向代理

安装 Nginx:

bash 复制代码
sudo apt update
sudo apt install nginx

创建配置文件:

text 复制代码
/etc/nginx/sites-available/order-api

配置如下:

nginx 复制代码
server {
    listen 80;
    listen [::]:80;

    server_name api.example.com;

    client_max_body_size 10m;

    access_log /var/log/nginx/order-api-access.log;
    error_log /var/log/nginx/order-api-error.log;

    location / {
        proxy_pass http://127.0.0.1:18000;

        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Request-Id $request_id;

        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

重点是:

nginx 复制代码
proxy_pass http://127.0.0.1:18000;

因为 Nginx运行在宿主机上,所以需要访问宿主机映射后的 18000 端口。

启用配置:

bash 复制代码
sudo ln -s /etc/nginx/sites-available/order-api \
  /etc/nginx/sites-enabled/order-api

检查 Nginx 配置:

bash 复制代码
sudo nginx -t

重新加载:

bash 复制代码
sudo systemctl reload nginx

测试:

bash 复制代码
curl http://api.example.com/health

十四、配置 HTTPS

安装 Certbot:

bash 复制代码
sudo apt install certbot python3-certbot-nginx

申请证书:

bash 复制代码
sudo certbot --nginx -d api.example.com

测试 HTTPS:

bash 复制代码
curl https://api.example.com/health

检查证书自动续期:

bash 复制代码
sudo certbot renew --dry-run

配置完成后,业务方使用:

text 复制代码
https://api.example.com

不需要知道 Docker 内部端口或宿主机的 18000 端口。


十五、不使用 Docker Compose 的写法

如果不使用 Docker Compose,可以通过 docker run 映射端口:

bash 复制代码
docker run -d \
  --name order-api \
  --restart unless-stopped \
  -p 127.0.0.1:18000:8000 \
  order-api:1.0.0

其中:

text 复制代码
-p 127.0.0.1:18000:8000

与 Compose 中的配置完全相同:

yaml 复制代码
ports:
  - "127.0.0.1:18000:8000"

踩坑记录

问题一:Docker Hub连接超时

错误信息:

text 复制代码
Client.Timeout exceeded while awaiting headers

或者:

text 复制代码
failed to resolve source metadata for docker.io/library/python:3.12-slim

这说明 Docker 无法正常访问:

text 复制代码
https://registry-1.docker.io/v2/

可以尝试:

  1. 为 Docker Desktop 配置代理;
  2. 使用公司内部 Harbor;
  3. 使用可访问的公共镜像仓库;
  4. 在其他机器下载后离线导入。

本文使用:

dockerfile 复制代码
FROM public.ecr.aws/docker/library/python:3.12-slim

作为替代。


问题二:pip install 十几分钟没有完成

如果构建长时间停留在:

text 复制代码
RUN pip install ...

一般是容器访问 PyPI 速度较慢或直接超时。

使用下面的命令可以查看详细日志:

bash 复制代码
docker compose build --no-cache --progress=plain

如果日志中反复出现:

text 复制代码
Retrying
ReadTimeoutError
Connection timed out

可以配置:

dockerfile 复制代码
ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple

如果通过本机代理构建,假设代理端口是 7890:

bash 复制代码
docker compose build \
  --build-arg HTTP_PROXY=http://host.docker.internal:7890 \
  --build-arg HTTPS_PROXY=http://host.docker.internal:7890 \
  --no-cache \
  --progress=plain

Mac Docker Desktop 中,容器访问宿主机需要使用:

text 复制代码
host.docker.internal

不能使用:

text 复制代码
127.0.0.1

因为容器中的 127.0.0.1 指向容器自身。


问题三:Dockerfile 出现 unknown instruction

错误信息:

text 复制代码
unknown instruction: "fastapi"

原因通常是 CMD 被错误拆行。

推荐写法:

dockerfile 复制代码
CMD ["fastapi", "run", "app/main.py", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips=*"]

问题四:宿主机端口被占用

错误信息:

text 复制代码
port is already allocated

Linux 查看端口:

bash 复制代码
sudo ss -lntp | grep 18000

Mac 查看端口:

bash 复制代码
lsof -nP -iTCP:18000 -sTCP:LISTEN

解决方法:

  • 停止占用端口的进程;
  • 或者更换宿主机端口。

例如:

yaml 复制代码
ports:
  - "127.0.0.1:18001:8000"

问题五:修改端口后没有生效

Docker 容器创建后,端口映射不能直接动态修改。

修改 compose.yaml 后,需要重新创建容器:

bash 复制代码
docker compose up -d --force-recreate

问题六:健康检查失败

健康检查运行在容器内部,因此应该访问:

text 复制代码
http://127.0.0.1:8000/health

不能写成:

text 复制代码
http://127.0.0.1:18000/health

18000 是宿主机端口,8000 才是容器内部端口。


十六、常用命令汇总

启动服务:

bash 复制代码
docker compose up -d

停止并删除容器:

bash 复制代码
docker compose down

重新构建:

bash 复制代码
docker compose build --no-cache --progress=plain

重新创建容器:

bash 复制代码
docker compose up -d --build --force-recreate

查看状态:

bash 复制代码
docker compose ps

查看日志:

bash 复制代码
docker compose logs -f api

查看端口映射:

bash 复制代码
docker port order-api

测试服务:

bash 复制代码
curl http://127.0.0.1:18000/health

总结

本次部署最关键的是理解下面三个端口之间的关系:

text 复制代码
Nginx 80/443
      ↓
宿主机 127.0.0.1:18000
      ↓
Docker 容器 8000
      ↓
FastAPI

Dockerfile 中定义 FastAPI 使用的容器端口:

dockerfile 复制代码
EXPOSE 8000

Docker Compose 完成端口映射:

yaml 复制代码
ports:
  - "127.0.0.1:18000:8000"

Nginx访问映射后的宿主机端口:

nginx 复制代码
proxy_pass http://127.0.0.1:18000;

本机测试地址:

text 复制代码
http://127.0.0.1:18000/health

最终提供给业务方的地址:

text 复制代码
https://api.example.com

使用这种部署方式,可以避免直接把 FastAPI 容器端口暴露到公网,同时保留 Docker 部署、升级和回滚的便利性。

文章标签:

text 复制代码
Python、FastAPI、Docker、Docker Compose、Nginx、接口部署、端口映射
相关推荐
不爱吃鱼的蔓热莓1 小时前
Autosar arxml异常字符串处理
python
FBI HackerHarry浩1 小时前
Pytorch中 CPU tensor vs GPU tensor
人工智能·pytorch·python·深度学习
wewowilling1 小时前
工作流 .... coze平台--AI--Agent--RAG--Function Calling
python·低代码·agent·零代码·工作流·coze
小草cys1 小时前
图片一键转全矢量可编辑PPT——纯Python实现,零依赖,离线可用
人工智能·python·powerpoint
东莞市云毅网络有限公司1 小时前
客服记录里的问句怎么变成知识库条目:意图聚类的 Python 实现
python·sqlite·自动化运维·geo·数据监测
java_logo1 小时前
Docker 部署 Halo:轻松搭建开源个人博客
docker·容器·开源·博客·开源博客·halo·轩辕镜像
小马不起床2 小时前
零基础玩转 FastAPI_12:前端详情增强与角色权限分配
前端·状态模式·fastapi
Linux实战笔记2 小时前
Docker多阶段构建优化生产配置手记:镜像瘦身最佳实践
ci/cd·docker·生产环境·多阶段构建·镜像瘦身
严谨的麻辣烫2 小时前
为什么 API 接口偶尔报错,重试一次又恢复了?从 HTTP 连接池到 Keep-Alive 排查
python·网络协议·tcp/ip·http·性能优化