全家桶装到一半,我寻思着:光装别人的软件有啥意思,把自己做的站也搬进来——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 的盒子到底扛不扛得住。