腾讯云 Ubuntu 24.04 + Docker 同时部署多个 Python Web 项目:没有域名也能用 IP 访问
这篇文章整理的是一次真实部署过程。场景很常见:手里有一台腾讯云服务器,系统是 Ubuntu Server 24.04 LTS 64 位,镜像里已经预装了 Docker。暂时没有买域名,只有公网 IP,但又想让多个 Web 项目同时跑起来,并且都能访问。
我这次用的是"一个项目一个端口"的方式,适合课程项目、答辩项目、团队内部演示项目。等后面有域名了,再升级成 Nginx 反向代理、HTTPS、子域名访问。

一、这次要实现什么效果
服务器公网 IP:
text
58.87.80.114
最终希望几个项目可以同时访问:
| 项目 | 访问地址 | 说明 |
|---|---|---|
| health-middle | http://58.87.80.114 |
占用服务器 80 端口 |
| projectdream | http://58.87.80.114:8002 |
占用服务器 8002 端口 |
| ai-manga-2 | http://58.87.80.114:8001 |
预留给另一个项目 |
注意,这里不是买了多个服务器,而是一台服务器跑多个项目。
每个项目一个 Docker Compose,每个项目暴露不同的外部端口,就可以互不影响。
二、先登录服务器,检查 Docker 是否正常
登录服务器后,先不要急着启动项目,先确认自己在哪个目录、Docker 是否可用。
bash
pwd
docker -v
docker compose version
ls
我这里看到的结果如下:

可以看到:
text
当前目录:/home/ubuntu
Docker version 27.5.1
Docker Compose version v2.32.4
目录里已经有几个项目:
text
ai-manga-2
health-middle
projectdream
这一步主要确认两件事:
- Docker 能用。
- 项目文件确实在服务器上。
如果 docker -v 或 docker compose version 没有输出版本号,就说明 Docker 环境还没配好,要先解决 Docker 安装问题。
三、统一整理项目目录
项目一多,如果都堆在 /home/ubuntu 下,后面会越来越乱。
我习惯统一放到:
text
/opt/projects
先创建目录,并把权限交给当前的 ubuntu 用户:
bash
sudo mkdir -p /opt/projects
sudo chown -R ubuntu:ubuntu /opt/projects

然后把项目移动进去:
bash
mv ~/ai-manga-2 /opt/projects/
mv ~/health-middle /opt/projects/
mv ~/projectdream /opt/projects/
移动后检查:
bash
ls /opt/projects
正常应该能看到:
text
ai-manga-2 health-middle projectdream
这样以后找项目就很清楚:
text
/opt/projects/
├── ai-manga-2
├── health-middle
└── projectdream
四、先检查第一个项目 health-middle
进入项目目录:
bash
cd /opt/projects/health-middle
ls
docker compose ps
我这里看到 health-middle 已经跑起来了:

重点看 STATUS:
text
backend Up ... healthy
frontend Up ... healthy
只要后端和前端都是 healthy,说明项目本身基本正常。
再看端口:
text
0.0.0.0:80->8080/tcp
这句话的意思是:
text
服务器的 80 端口 -> 容器内部的 8080 端口
所以浏览器访问:
text
http://58.87.80.114
就会进入 health-middle 项目。
五、启动第二个项目 projectdream 时遇到的第一个问题
进入第二个项目:
bash
cd /opt/projects/projectdream
ls
docker compose ps
这时候我遇到了一个报错:
text
MYSQL_ROOT_PASSWORD is missing a value
大白话解释就是:
text
docker-compose.yml 里要用 MYSQL_ROOT_PASSWORD,
但是 Docker Compose 没有读到这个变量。
这个问题非常常见,基本就是环境变量文件没读到。
六、重点讲一下 .env 和 .env.production
很多人第一次部署 Docker Compose 项目,最容易卡在这里。
1. 默认情况下,Docker Compose 会自动读取 .env
如果项目目录里有:
text
.env
那通常直接执行:
bash
docker compose up -d --build
Docker Compose 就能自动读取 .env 里的变量。
比如 .env 里写:
env
MYSQL_ROOT_PASSWORD=换成自己的数据库root密码
MYSQL_DATABASE=projectdream
MYSQL_USER=projectdream
MYSQL_PASSWORD=换成自己的数据库用户密码
HTTP_PORT=8002
docker-compose.yml 里就可以这样用:
yaml
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}
2. 但 .env.production 不会被默认读取
我这个项目里不是 .env,而是:
text
.env.production
可以通过下面命令查看:
bash
ls -a

这里要注意:
text
.env 默认会被 Docker Compose 自动读取
.env.production 不会自动读取,需要手动指定
所以如果项目使用 .env.production,启动时要这样写:
bash
docker compose --env-file .env.production up -d --build
查看状态也要这样写:
bash
docker compose --env-file .env.production ps
如果你嫌每次都写 --env-file .env.production 麻烦,也可以复制一份成 .env:
bash
cp .env.production .env
docker compose up -d --build
不过我更推荐保留 .env.production,这样一看就知道这是生产环境配置。
3. 怎么判断环境变量有没有配上
比如报错说:
text
MYSQL_ROOT_PASSWORD is missing a value
那就检查:
bash
grep MYSQL_ROOT_PASSWORD .env
如果你用的是 .env.production,就检查:
bash
grep MYSQL_ROOT_PASSWORD .env.production
能看到类似下面这种就说明文件里有:
env
MYSQL_ROOT_PASSWORD=换成自己的数据库root密码
如果没有输出,就说明变量没写进去。
4. 一个简单口诀
这块可以直接记住:
text
文件叫 .env:docker compose 会自动读
文件不叫 .env:必须加 --env-file 文件名
变量缺失:先 grep 检查环境文件
端口冲突:优先改冒号左边
冒号右边:一般是容器内部端口,不要随便动
七、第二个问题:80 端口被占用了
环境变量解决后,再启动 projectdream:
bash
docker compose --env-file .env.production up -d --build
又遇到了一个新报错:
text
Bind for 0.0.0.0:80 failed: port is already allocated

这个报错其实很好理解:
text
health-middle 已经占用了服务器 80 端口,
projectdream 也想占用 80 端口,
一台服务器的同一个端口不能同时给两个项目用。
所以要给第二个项目换一个外部端口,比如:
text
8002
八、怎么改项目访问端口
打开 projectdream 的 docker-compose.yml:
bash
nano docker-compose.yml
我这里前端端口配置是这样的:
yaml
ports:
- "${HTTP_PORT:-80}:8080"

这句非常关键,拆开看:
text
${HTTP_PORT:-80} 表示读取 HTTP_PORT,如果没配置就默认用 80
8080 表示容器内部前端服务监听 8080
也就是说:
yaml
ports:
- "${HTTP_PORT:-80}:8080"
如果 .env.production 里没有配置 HTTP_PORT,最终就等于:
yaml
ports:
- "80:8080"
这就会和 health-middle 冲突。
解决方法是编辑 .env.production:
bash
nano .env.production
加一行:
env
HTTP_PORT=8002
保存后重新启动:
bash
docker compose --env-file .env.production up -d
docker compose --env-file .env.production ps
如果看到类似:
text
0.0.0.0:8002->8080/tcp
就说明端口映射成功了。
九、浏览器打不开 8002 是什么原因
项目启动后,我访问:
text
http://58.87.80.114:8002
浏览器出现:
text
ERR_CONNECTION_TIMED_OUT

这个时候先不要急着怀疑代码。
如果服务器里 docker compose ps 已经显示:
text
0.0.0.0:8002->8080/tcp
但浏览器还是访问超时,最常见原因就是:
text
腾讯云安全组没有放行 8002 端口。
去腾讯云控制台添加入站规则:
text
协议端口:TCP:8002
来源:0.0.0.0/0
策略:允许
保存后再刷新:
text
http://58.87.80.114:8002
基本就能访问了。
这里总结一下判断思路:
text
浏览器超时
不是项目页面报错
而是浏览器根本没连到服务
优先检查:端口映射 + 云服务器安全组
十、常用 Docker Compose 命令
进入某个项目目录后使用。
1. 查看容器状态
bash
docker compose ps
如果使用 .env.production:
bash
docker compose --env-file .env.production ps
2. 启动并重新构建
bash
docker compose up -d --build
如果使用 .env.production:
bash
docker compose --env-file .env.production up -d --build
3. 停止项目
bash
docker compose down
如果使用 .env.production:
bash
docker compose --env-file .env.production down
4. 查看日志
bash
docker compose logs -f
只看前端:
bash
docker compose logs -f frontend
只看后端:
bash
docker compose logs -f backend
5. 重启项目
bash
docker compose restart
十一、多项目部署时,我建议这样分配端口
没有域名时,最简单的方式就是:
text
一个项目 = 一个端口
比如:
| 项目 | 外部端口 | 访问地址 |
|---|---|---|
| health-middle | 80 | http://58.87.80.114 |
| ai-manga-2 | 8001 | http://58.87.80.114:8001 |
| projectdream | 8002 | http://58.87.80.114:8002 |
注意:
text
不同项目的外部端口不能重复。
如果两个项目都写:
yaml
ports:
- "80:8080"
一定会冲突。
正确做法是:
yaml
ports:
- "8001:8080"
或者:
yaml
ports:
- "8002:8080"
冒号左边是服务器端口,可以改。
冒号右边是容器内部端口,一般跟项目 Dockerfile 或 Nginx 配置有关,不懂的时候先别乱动。
十二、最后整理一份操作清单
以后成员照着这份顺序来就行。
1. 登录服务器
bash
ssh ubuntu@服务器公网IP
2. 检查 Docker
bash
docker -v
docker compose version
3. 创建统一项目目录
bash
sudo mkdir -p /opt/projects
sudo chown -R ubuntu:ubuntu /opt/projects
4. 移动项目
bash
mv ~/项目名 /opt/projects/
5. 进入项目
bash
cd /opt/projects/项目名
6. 看有没有环境文件
bash
ls -a
如果是 .env:
bash
docker compose up -d --build
如果是 .env.production:
bash
docker compose --env-file .env.production up -d --build
7. 如果提示变量缺失
bash
grep 变量名 .env
或者:
bash
grep 变量名 .env.production
8. 如果提示 80 端口冲突
去环境文件里加:
env
HTTP_PORT=8002
然后重新启动:
bash
docker compose --env-file .env.production up -d
9. 如果浏览器访问超时
检查两件事:
bash
docker compose --env-file .env.production ps
看有没有:
text
0.0.0.0:8002->8080/tcp
然后去腾讯云安全组放行:
text
TCP:8002
十三、总结
这次部署下来,我感觉没有域名时,最稳的做法就是:
text
一台服务器
多个项目目录
每个项目一个 docker-compose.yml
每个项目一个外部端口
不要一开始就上复杂的 Nginx 路径转发,特别是 Vue、React 这类前端项目,路径前缀一不注意就会出静态资源问题。
先用:
text
http://服务器IP
http://服务器IP:8001
http://服务器IP:8002
把项目跑通。
等后面有域名了,再升级成:
text
demo.xxx.com
api.xxx.com
admin.xxx.com
这样整个部署思路就清楚很多。
参考资料
- Docker Compose 环境变量与
--env-file:https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/ - Docker Compose 端口映射配置:
https://docs.docker.com/reference/compose-file/services/#ports