拒当 Docker 难民!超轻量 FastAPI + uv + Nginx + Systemd 生产级多项目部署保姆级实战

3 阅读7分钟

🚀 拒当 Docker 难民!超轻量 FastAPI + uv + Nginx + Systemd 生产级多项目部署保姆级实战

📌 前言

在如今“万物皆可 Docker”的时代,很多人部署个简单的 FastAPI 后端也习惯套个几百兆的容器镜像。但对于低配云服务器(如 1核2G 内存的入门机型)来说,Docker 守护进程和层层容器的额外内存、CPU 开销往往会让服务器不堪重负。

不用 Docker 难道就无法保证环境隔离和优雅部署了吗?

当然不是!本文将带你完整走通一条纯原生、极速、超轻量的生产级部署方案。我们将使用 Python 界的“速度妖魔” uv 代替传统 pip 解决依赖,用 Linux 原生的 Systemd 进行进程守护,再用 Nginx 进行路径前缀(Prefix)分流,并借助 Cursor/VS Code Remote-SSH 实现可视化远程运维

整套方案不仅内存占用极低,还能完美支持在一台服务器上无缝混部多个不同的后端项目!

🛠️ 技术栈与工具链

  • 操作系统:Ubuntu 22.04 / 24.04 LTS
  • 后端框架:FastAPI (Python 3.12+)
  • 依赖管理:Astral uv(性能怪兽,比传统 venv/pip 快数十倍)
  • 进程守护:Systemd
  • 反向代理:Nginx
  • 远程工具:Cursor / VS Code (Remote - SSH 插件)

🏗️ 部署架构图

第一阶段:准备工作与环境初始化

1. 更新系统并安装基础依赖

sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-pip python3-venv mysql-server nginx -y

2. 初始化 MySQL 数据库

进入 MySQL 创建项目专属数据库及用户:

sudo mysql -u root

-- 在 MySQL 交互命令行中执行:
CREATE DATABASE my_fastapi_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'fastapi_user'@'localhost' IDENTIFIED BY '你的强密码';
GRANT ALL PRIVILEGES ON my_fastapi_db.* TO 'fastapi_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

3. 安装 Astral uv 包管理器

uv 是目前最快的 Python 包管理工具,线上更新依赖几乎是瞬时完成。

# 一键安装
curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh
💡 避坑指南:环境变量未生效?

安装完毕后,如果终端提示 uv: command not found,说明环境变量尚未载入。直接运行以下命令,将 uv 的 bin 路径写入 Shell 配置文件,一劳永逸:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

输入 uv --version 验证,若正确显示版本号,说明准备工作顺利通过!

第二阶段:项目初始化与依赖管理

💥 踩坑一:Git 克隆后权限不足(Permission Denied)

在服务器上克隆代码或同步文件后,有时会遇到类似以下报错:

Failed to create virtual environment ... Permission denied (os error 13)

原因:项目目录的所有权属于 root,而当前登录的 ubuntu 普通用户没有写入权限。 解决办法:一键收回项目文件夹的所有权。

# 将路径替换为你的实际项目路径
sudo chown -R ubuntu:ubuntu /home/ubuntu/myresume_dify

💥 踩坑二:Python 3.12 保护机制(PEP 668)

在现代 Linux 系统中直接运行 pip install -r requirements.txt 会遭遇系统报错:

error: externally-managed-environment. This environment is externally managed...

原因:基于 PEP 668 协议,Ubuntu 锁定了系统全局 Python 环境,防止用户安装第三方包破坏系统组件。 解决办法:使用 uv 创建完全隔离的独立虚拟环境。

cd /home/ubuntu/myresume_dify/backend

# 1. 使用 uv 极速创建虚拟环境(默认文件夹名为 .venv)
uv venv

# 2. 使用 uv 安装项目所需的全部依赖
uv pip install -r requirements.txt

你会惊奇地发现,在 uv 的加持下,几十个依赖包仅用不到 3 秒就全部安全地下载并编译完成了!

第三阶段:使用 Systemd 进行进程守护

如果直接在前台用 uvicorn 启动项目,一旦关闭 SSH 终端窗口,程序就会挂掉。我们需要用 Linux 自带的 systemctl 将项目打包为后台守护服务。

1. 配置 Systemd 服务文件

sudo nano /etc/systemd/system/fastapi.service

💥 踩坑三:Bad message / Bad unit file setting 报错

编辑服务文件时,若无意中将行连接到了一起(如把 [Unit] 和后面的属性写在了同一行),在重启服务时会遭遇以下报错:

Unit fastapi.service failed to load properly ... Bad message

务必确保分行格式精准无误,直接复制并编辑以下标准模板:

[Unit]
Description=FastAPI myresume_dify Backend
After=network.target

[Service]
User=ubuntu
Type=simple
# 你的后端项目绝对路径(存放 main.py 的目录)
WorkingDirectory=/home/ubuntu/myresume_dify/backend
# 指向 .venv 虚拟环境中的 uvicorn 绝对路径,并传入 --root-path 锁定路由前缀
ExecStart=/home/ubuntu/myresume_dify/backend/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8188 --workers 4 --root-path /myresume
Restart=always

[Install]
WantedBy=multi-user.target
💡 核心设计:--root-path 的妙用

在一台服务器部署多个项目时,通常需要加前缀区分(如 /myresume)。如果不加 --root-path /myresume 参数,当 Nginx 将请求转发给 FastAPI 时,FastAPI 将无法感知到这个前缀,会导致 Swagger UI 的 openapi.json 报 404 错误。

2. 启动并激活服务

# 刷新 Systemd 配置以识别新服务
sudo systemctl daemon-reload

# 启动 FastAPI 服务
sudo systemctl start fastapi.service

# 设置开机自启
sudo systemctl enable fastapi.service

# 查看运行状态
sudo systemctl status fastapi.service

当看到醒目的绿色 active (running) 并且日志提示 Application startup complete.,说明后端已经成功驻留在系统后台!

第四阶段:使用 Cursor 可视化配置 Nginx 前缀分流

由于 /etc/nginx/ 属于系统高保密目录,普通用户无权直接编辑。在这里,我们将利用 Cursor / VS Code 的 Remote - SSH 插件进行可视化运维。

1. 可视化远程连接

  1. 在本地 Cursor/VS Code 中,安装 Remote - SSH 插件。
  2. 点击左下角蓝色图标 Connect to Host...,输入 ubuntu@你的公网IP 进行安全连接。
  3. 连接成功后,选择 Open Folder,直接定位到服务器的 Nginx 配置目录:/etc/nginx/sites-available

2. 编辑 default 配置文件

双击打开 default 配置文件,在 server { ... } 块中加入我们的反向代理路由。通过这种方式,我们可以在同一个 80 端口上挂载无数个后端,只需前缀不同即可!

server {
    listen 80;
    server_name 101.34.8.1; # 替换成你的公网 IP 或域名

    # ----------------------------------------------------
    # 项目 A:我的简历项目 (转发至 8188 端口)
    # ----------------------------------------------------
    location /myresume {
        proxy_pass [http://127.0.0.1:8188](http://127.0.0.1:8188); # 注意:127.0.0.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;
    }

    # ----------------------------------------------------
    # 项目 B:未来的另一个新项目 (比如监听 8200 端口)
    # ----------------------------------------------------
    location /other-project {
        proxy_pass [http://127.0.0.1:8200](http://127.0.0.1:8200); 
        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;
    }
}
💥 踩坑四:文件无法保存(Permission Denied - EACCES)

保存时会弹框报错:permission denied, open '/etc/nginx/sites-available/default'极速解决:直接点击 Cursor 右下角弹出的蓝色按钮 Retry as Sudo(以管理员身份重试) ,输入服务器用户密码即可无感保存!

3. 热重启 Nginx

在 Cursor 中直接按下 `Ctrl + `` 打开内置终端,输入:

# 验证 Nginx 配置文件语法是否正确
sudo nginx -t

# 重新加载配置
sudo systemctl restart nginx

第五阶段:公网测试与日常运维

1. 验证公网访问

确保你的云服务器后台(如腾讯云、阿里云控制台)的安全组/防火墙中,放行了 80 端口(常规网页服务端口)。 此时,直接在你的本地浏览器中输入: http://你的服务器公网IP/myresume/docs

当那个经典的蓝绿相间的 Swagger UI API 交互文档界面跃然纸上时,恭喜你,项目部署彻底大功告成!

2. 核心运维工具箱

后端开发和运维往往相伴相生,掌握以下三个指令可以帮你解决 90% 以上的线上问题:

  • 查看实时日志(排查 Python 逻辑报错的终极利器):

    sudo journalctl -u fastapi.service -n 50 -f
    
  • 重启后端服务(每次 git pull 更新代码后运行):

    sudo systemctl restart fastapi.service
    
  • 下线/彻底删除服务

    sudo systemctl stop fastapi.service
    sudo systemctl disable fastapi.service
    sudo rm /etc/systemd/system/fastapi.service
    sudo systemctl daemon-reload
    

💡 结语

在这篇文章中,我们没有引入庞大的 Docker,而是基于最原生的 Linux 链路,打通了 FastAPI (uv) -> Systemd -> Nginx -> Cursor 可视化 的全闭环部署架构。 这套方案不仅极度节省云服务器的内存,而且得益于 Nginx 的路径分流设计,未来添加新项目只需在 Nginx 中叠加 location 配置,实现了极强的横向扩展能力。

希望本篇保姆级教程对你有所帮助!如果你在部署中遇到了其他神奇的报错,欢迎在评论区一起交流探讨!