全家桶装到一半,我寻思着:光装别人的软件有啥意思,把自己做的站也搬进来——tanqu.cc,我自己的短剧社区站,从裸机部署改成 Docker 部署,顺便把踩坑全记下来。
这篇是全家桶系列里最"实战"的一篇,因为涉及前后端 + 数据库 + 反代理的完整架构,看懂这篇,你基本能部署任何 Web 应用。
先说 tanqu.cc 是啥
我自己做的一个短剧社区站,用户能在上面看短剧、讨论剧情,有用户系统、播放页、后台管理。之前是裸机部署:Ubuntu 装 Node.js、装 MySQL、手动跑进程,升级一次麻烦得要死,换个机器更是噩梦。
这次借着搭全家桶,干脆全部容器化——以后换机器,一个 compose 文件拉起来,半小时恢复。
架构是这样:
| 组件 | 作用 | 容器 |
|---|---|---|
| 前端页面 | 用户看的网页 | Nginx 静态容器 |
| 后端 API | 接口服务 | Node.js 容器 |
| 数据库 | 用户/内容数据 | MySQL 容器 |
| 视频文件 | 短剧视频 | 挂载目录(视频量大,不装容器里) |
目录规划
/opt/docker/tanqu
├── frontend/ # 前端构建产物
├── backend/ # 后端代码
├── mysql/ # 数据库数据
├── videos/ # 视频文件(大文件,单独磁盘)
└── docker-compose.yml
视频文件是最大的坑——短剧几百集,动辄几十 GB,不能塞进容器,我直接挂载服务器上的视频目录,容器只负责"指路"。
docker-compose.yml 完整版
直接上配置文件,四个服务:
version: "3.8"
services:
mysql:
image: mysql:8.0
container_name: tanqu-mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=换成你的强密码
- MYSQL_DATABASE=tanqu
volumes:
- /opt/docker/tanqu/mysql:/var/lib/mysql
ports:
- "3306:3306"
backend:
image: node:20-alpine
container_name: tanqu-backend
restart: unless-stopped
working_dir: /app
volumes:
- /opt/docker/tanqu/backend:/app
command: sh -c "npm install && node server.js"
depends_on:
- mysql
ports:
- "8000:8000"
environment:
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=换成你的强密码
frontend:
image: nginx:alpine
container_name: tanqu-frontend
restart: unless-stopped
volumes:
- /opt/docker/tanqu/frontend:/usr/share/nginx/html:ro
ports:
- "8001:80"
depends_on:
- backend
nginx-proxy:
image: nginx:alpine
container_name: tanqu-proxy
restart: unless-stopped
volumes:
- /opt/docker/tanqu/nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- "80:80"
depends_on:
- frontend
- backend
几个关键点拆开讲:
backend 依赖 mysql:depends_on 保证启动顺序,但 MySQL 真正就绪要等几秒,所以后端代码里要写重试逻辑(连接失败自动重试),不然一启动就连不上库。
frontend 挂载静态文件:前端构建好的 HTML/JS/CSS 直接挂进 Nginx 容器,只读挂载,改文件不用重建容器。
nginx-proxy 统一入口:最外层一个 Nginx 做反向代理,用户只访问 80 端口,由它把请求分发给前端(静态页)和后端(API 接口)。
反代理配置
/opt/docker/tanqu/nginx.conf,核心就这一段:
server {
listen 80;
server_name tanqu.cc;
# 前端静态页面
location / {
proxy_pass http://frontend:80;
proxy_set_header Host $host;
}
# 后端 API
location /api/ {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 视频文件(直接读磁盘,不走后端)
location /media/ {
alias /opt/docker/tanqu/videos/;
}
}
这里有个细节:容器之间用容器名通信(http://frontend:80、http://backend:8000),这是 Docker 内部网络自动解析的,不用写 IP,换机器也不受影响。
视频文件单独一个 location,直接从磁盘 alias 出去,不经过后端,播放性能好,后端也不用扛大流量。
启动和验证
cd /opt/docker/tanqu
docker compose up -d
看状态:
docker compose ps
四个容器都 Up 就成功了。浏览器打开 http://你的服务器IP,能出页面就是前端通了;再测 API:
curl http://你的服务器IP/api/health
返回正常 JSON 就全通了。
踩过的坑,全是泪
坑 1:数据库连不上
一启动后端就报错连不上 MySQL。原因:MySQL 容器初始化要几十秒,后端启动太快,连的时候库还没好。解决:后端代码加个连接重试循环,每 2 秒重试一次,最多 30 次。别指望 depends_on,它只保证"启动顺序",不保证"就绪"。
坑 2:视频播放不了
短剧视频在服务器上,页面请求 /media/xxx.mp4 却 404。排查半天,是 nginx 的 alias 路径写错了。alias 的路径要和实际目录完全对上,结尾的斜杠也不能少。 改对之后,播放正常。
坑 3:端口被占用
之前裸机部署的旧进程还占着 80 端口,新 Nginx 容器起不来。解决:先 lsof -i :80 找到旧进程,停掉,再 docker compose up -d。换容器化之前,先把裸机上的旧服务全停干净。
坑 4:视频格式播放兼容
短剧视频有的老格式浏览器播不了。我的处理:统一转成 MP4(H.264 编码),页面用原生 video 标签播放,不依赖任何插件。这个之前踩过,专门写过,这里就不展开了。
结尾
tanqu.cc 容器化完成之后,整个 Mac mini 全家桶齐了:网盘、下载器、影视库、聚合挂载、管理面板、还有我自己的站,全在 Docker 里统一管理。
现在给朋友演示特别有底气:这台 13W 的盒子里,住着我自己的网站、朋友的网盘、还有一套影视库。
tanqu.cc 本身也欢迎大家来逛逛,看看我自己做的短剧社区长啥样。
全家桶系列更新在 moxiaoa.com,想搭服务器、要配置文件、或者对短剧站感兴趣的,加我微信 lige0552。
下一篇:功耗实测——全家桶全开,13W 的盒子到底扛不扛得住。