首页 / 资讯中心 / 文章详情

宝塔面板Docker镜像固化:内网离线部署与Nginx反代全流程

宝塔面板Docker镜像固化:内网离线部署与Nginx反代全流程 ★ FEATURED ARTICLE
“宝塔面板做成 Docker 镜像固化后导出到局域网直接使用”——这套流程听着挺小众但实际做下来非常上头。我最近刚好在部门内网环境里把这个方案完整跑通了一次。场景是这样的有几台服务器只能访问内网外网基本不通但开发、演示都要统一的 Linux 站点环境手工一台台装宝塔装 Nginx 再配站折腾一次至少一两个小时还不算中间踩的依赖坑。于是我就想能不能在能联网的机器上把宝塔面板连 Nginx 一起装进 Docker做成镜像导出去内网机器拿到镜像直接用。答案是可以而且跑通之后效率高得离谱。这篇就把整个思路、命令、坑和可复用的配置都拆开讲清楚。1. 为什么要在局域网折腾一个宝塔镜像1.1 这个项目解决的真实痛点先说需求本质。开发测试环境、内网演示环境、边缘机房的服务器最常见的困境就是“没有外网”。你在一台新机器上装宝塔面板看起来只是跑个安装脚本实际上安装过程会去拉一堆系统依赖、PHP 扩展、Nginx 编译需要的源码包。外网一断安装进度直接卡死在下载那一步。就算侥幸装完也不保证两台机器上的版本、配置、站点是完全一致的。Docker 镜像恰恰解决的就是“一次构建、处处运行”的问题。把宝塔面板、Nginx、PHP、站点初始配置全部打进一个镜像导成 tar 包通过 U 盘、局域网共享、HTTP 下载都行。目标机器只需要有 Docker一条 docker load 加一条 docker run几分钟内就能得到一个面板可用、站点可访问的完整环境。这个方案的本质相当于把一台已经装好宝塔、装好 Nginx、建好测试站的服务器整个“拍快照”封起来然后在另一台机器上“解压还原”。和虚拟机模板一个道理只不过用的是 Docker 的镜像机制轻量不少。1.2 方案对比为什么选择“手动装 commit save”当时我列了三种思路可以给你参考。方案优点缺点适合场景每台机器原生命令装宝塔无额外学习成本无外网环境下基本装不上多次安装版本不一致单台机器、外网通畅直接拉现成的宝塔 Docker 镜像省事一条 pull 搞定镜像源必须可达版本、组件不可控内网环境照样拉不动临时测试、外网可控自己构建镜像并导出分发一次构建处处可用完全离线部署配置可定制初次构建耗时长镜像体积偏大需要理解 Docker 基本机制局域网批量部署、离线环境我最终选了第三种。理由很现实我手里的内网机器连 Docker Hub 都连不上更别提拉什么现成镜像。自己做镜像的过程虽然第一次比较折腾但构建完成之后后续复制环境的边际成本几乎为零。具体到“怎么构建”又有一个重要的选择用 Dockerfile 自动化构建还是进入容器手动装完再用 docker commit 固化成镜像。Dockerfile 当然更“正经”可复现性更好。但宝塔的安装过程有随机安全入口、有交互式输出、有面板初始账号密码生成而且安装时间很长期间往往要手动处理各种依赖问题。这种情况下硬写 Dockerfile 反而是在给自己添堵。所以我选择了手动进容器安装装完配置好再 docker commit。打个比方Dockerfile 像流水线生产适合产品需要反复重建docker commit 像请师傅手工做完一件家具再整体翻模适合你要的是“这台机器的当前状态”。宝塔这种带大量运行时生成文件的东西明显更适合后者。1.3 Nginx 放在哪里两种拓扑怎么选标题里明确有“dockernginx”这里 Nginx 的角色就必须先说清楚否则后面配置会乱。我见过两种做法各有各的道理。第一种是最直接的容器内部装宝塔自带的 Nginx站点监听 80 和 443docker run 的时候把宿主机的 80、443 端口直接映射到容器。访问宿主机 IP 就等于访问容器里的网站。这种做法简单适合容器独占宿主机端口、不打算做多站点入口管理的场景。第二种是宿主机再装一层 Nginx监听 80 和 443把请求反向代理到容器内部 Nginx 的 8080 端口。也就是宿主机 Nginx 做入口网关容器内 Nginx 做实际站点服务。好处很明显宿主机这层可以统一做 SSL 证书、日志记录、访问限流后面容器重启、IP 变化对访问方来说入口 IP 始终不变如果同一个宿主机还要跑其他业务也可以按域名分流。我最终采用的是第二种。至于原因还是回到“内网运维”这个场景——你不可能每次重启容器都去通知开发人员换 IP。入口稳定比什么都重要。下面的实操也会按这个拓扑来讲。2. 动手前必须想清楚的三件事2.1 基础镜像和容器启动方式很多人第一次在 Docker 里装宝塔直接docker run -it centos:7 bash进去就跑安装脚本结果各种诡异问题安装到一半服务起不来、面板打不开、重启容器后宝塔彻底失联。根源绝大多数出在“PID 1 进程”和“systemd”的问题上。普通容器的 PID 1 就是你的 shell 或者某个前台进程。而宝塔面板需要管理 Nginx、MySQL、PHP 一堆服务靠的是 systemd 或者 init 脚本。容器里如果只有 bash就没有一个完整的服务管理机制服务就算装上也无法自启和维护。解决办法是用/usr/sbin/init作为容器的启动命令让 systemd 成为容器内的 PID 1。配合--privilegedtrue给足权限宝塔要操作 iptables、挂载、系统参数等才能基本模拟一台真实服务器的环境。基础镜像选择上我用的是centos:7。宝塔对 CentOS 系的兼容性历史最久yum 源问题也好解决。Ubuntu、Debian 也能跑但遇到奇怪问题的概率高一些内网环境下排查成本更高不如直接用 CentOS 7 省心。2.2 端口规划端口规划是整个镜像方案中最容易忽略、后期最头疼的环节。宝塔面板默认端口是 8888面板内置的 phpMyAdmin 常走 888 端口Web 站点走 80/443FTP 走 20/21 以及一大段被动端口。如果这些端口在宿主机上已经被占用那端口映射就得重新规划。下面是我这次的端口分配直接贴出来供参考。端口用途本次处理方式8888宝塔面板入口映射到宿主机 8888可内网访问888phpMyAdmin映射到宿主机 888非必需可关闭80容器内站点监听容器内改为 8080不直接占宿主机 80443HTTPS 站点映射到宿主机 443或由宿主机 Nginx 承担按需8080容器内 Nginx 站点端口映射到宿主机 8080作为宿主机 Nginx 的反代目标20/21FTP 服务按需映射本次省略仔细看会发现我没有把宿主机 80 映射给容器 80。这是因为宿主机 80 要留给宿主机 Nginx 做统一入口。容器内站点监听 8080宿主 Nginx 再反代过去。这样 80 端口只属于宿主机 Nginx不会和容器内服务打架也不会出现“宿主机 Nginx 和容器 Nginx 都想占 80”的僵局。思路一定要在开始动手前定下来。后面如果中途想改拓扑容器内部 Nginx 配置和宿主机 Nginx 配置都要动费时费力。2.3 容器安全与面板加固内网环境不等于绝对安全。镜像一旦分发到多个节点面板的账号密码、安全入口就是所有节点的共同入口。所以我在构建镜像时做的第一件加固就是改默认安全入口、换强密码、绑定访问 IP。宝塔的默认安全入口是一串随机字符串安装的时候会展示。建议把这串字符串直接改成固定的、好记但不好猜的路径比如在面板设置里改安全入口为/panel_admin_xxx。同时关闭面板的“离线模式”以外的多余对外服务比如不需要的 phpMyAdmin 端口就不要映射。还要注意一点镜像里保存的密码是构建时的密码。分发出去后各个节点如果共用同一个密码万一泄露就是批量泄露。所以不要在镜像里设置过于重要的生产密码。这个镜像定位为“内部开发与演示环境”生产数据和服务一律不放在这种镜像里。3. 从零到一容器内安装宝塔的完整过程3.1 先用一条 run 命令把容器拉起来前面理论讲了一堆现在开始实际操作。首先在能联网的机器上确认 Docker 已安装并正常启动然后执行这条命令创建容器docker run -itd \ --name bt-node \ --privilegedtrue \ -p 8888:8888 \ -p 8080:8080 \ -p 443:443 \ -p 888:888 \ centos:7 \ /usr/sbin/init参数逐一解释--name bt-node给容器起名后面 commit、exec 都用这个名字。--privilegedtrue给容器 root 权限宝塔在管理防火墙和系统服务时需要。-p 8888:8888映射面板端口。-p 8080:8080给容器内 Nginx 站点监听端口预留。-p 443:443留着以后上 HTTPS。-p 888:888映射宝塔的 phpMyAdmin 入口不需要的可以去掉。centos:7基础镜像。/usr/sbin/init最关键让 systemd 作为容器主进程。创建完用docker ps看容器是否在运行。如果状态是 Exited多半是/usr/sbin/init在当前内核环境有问题先用docker logs bt-node看日志再继续排查。3.2 容器内的基础环境准备容器起来后进入容器内部docker exec -it bt-node bash进去后第一件事是换 yum 源。因为 CentOS 7 的默认源已经有不少失效或极慢的情况直接装东西极其痛苦。我习惯换成国内常见的镜像源具体源地址根据你自己网络的实际情况配置也可以直接用官方源碰碰运气。换完源后执行yum install -y wget curl vim net-tools tar注意这里的每一项都有用。wget 和 curl 是装机必备等下要下载安装脚本vim 用来改配置net-tools 提供 ifconfig 和 netstat排查网络问题时离不了tar 是后面压缩备份的依赖。顺手把时区也改了不然后面板日志时间对不上ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime还有一个容易被忽略的点容器默认没有 swap而宝塔安装过程中很可能要编译 MySQL 或 PHP内存不够时直接 OOM。如果宿主机内存不大建议在docker run时加上内存限制或者提前给宿主机分配 swap 空间。我这次构建的机器有 8G 内存只装 Nginx 不装 MySQL 的情况下没问题但如果要装 MySQL至少保证 4G 以上可用内存否则编译到一半被 kill 是家常便饭。3.3 安装宝塔面板基础环境就绪后开始安装宝塔面板。宝塔官网提供在线一键安装脚本核心就是两条命令wget -O /tmp/install.sh 宝塔官方下载地址 bash /tmp/install.sh官方脚本会检测系统环境过程中会安装一大堆依赖输出几百行日志属正常现象。耐心等不要中途关终端。装完后终端底部会打印面板访问地址、用户名、密码、安全入口。把这段信息复制保存下来。如果你所在的环境连宝塔官方下载地址都不通那只能在有外网的机器上先把脚本下载好再通过内网传入容器。实际操作中我更推荐这种方式在宿主机上下载好脚本通过docker cp拷进容器再执行更加可控。安装完成后立刻验证面板服务状态ps aux | grep bt能看到宝塔面板相关进程在跑就说明基本成功。3.4 配置 Nginx 并创建一个测试站点面板装好之后打开浏览器访问http://宿主机IP:8888/安全入口首次进入会要求同意用户协议然后进入面板首页。在面板的软件商店里找到 Nginx点击安装。这里有个选择可以装 Nginx 1.x 稳定版也可以装 OpenResty我用的是 Nginx 稳定版兼容性最稳。Nginx 装好后打开“网站”页面添加站点。站点域名这里有个小技巧内网环境没有真实域名可以直接填宿主机的内网 IP或者填一个形如demo.local的假域名并在浏览器 hosts 里做解析。我建议填假域名配合 hosts 解析这样以后迁移到别的 IP 也不用改站点配置。关键一步创建站点时把端口从默认的 80 改到 8080。在站点设置里找到“域名”或“端口”配置改成8080。这一步对应前面说的拓扑——容器内 Nginx 监听 8080宿主机 80 由宿主机 Nginx 负责。创建完成后在容器内测试curl -I http://127.0.0.1:8080如果能收到 HTTP 200 响应说明容器内 Nginx 和站点已经正常工作。这个测试站点会在 commit 之后被带进镜像分发到其他机器后访问者的第一眼就是一个活站点而不是光秃秃的面板登录页。4. 宿主 Nginx 反代接入 镜像固化导出4.1 配置宿主机 Nginx 反向代理容器内部已经跑起来了现在回到宿主机层面。宿主机上安装 Nginx注意是宿主机上的 Nginx不是容器里的然后写一个 server 块把宿主机 80 端口的请求反代到容器内 Nginx 的 8080。配置示例server { listen 80; server_name demo.local 192.168.10.10; location / { proxy_pass http://127.0.0.1:8080; 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; } }这里192.168.10.10要换成你宿主机的实际内网 IP。proxy_pass指向127.0.0.1:8080是因为我们已经把容器 8080 映射到了宿主机 8080宿主机访问自己的 8080 就等于访问容器内 Nginx。配置好后重载 Nginxnginx -t nginx -s reload然后从局域网的另一台机器访问http://宿主机IP/如果能看到刚才创建的测试站点说明宿主机 Nginx 反代链路通了。这一步是整个方案里最体现“dockernginx”价值的地方。宿主机 Nginx 管入口容器 Nginx 管站点分离两层职责。以后不管容器怎么换 IP、怎么重启对外入口不变。4.2 提交镜像前先做瘦身容器功能验证完毕接下来要固化镜像。但直接 commit 会把容器里一大堆临时文件也一起带走yum 缓存、安装包、日志、临时下载文件镜像做出来动辄十几个 GB内网传输想哭。我的瘦身动作分三步。第一步在容器内清 yum 缓存和临时文件docker exec -it bt-node bash -c yum clean all rm -rf /var/cache/yum/*第二步清理宝塔自身的日志和备份缓存。站点日志在/www/wwwlogs目录下测试产生的访问日志全部清掉。安装包缓存一般在/root或者/tmp下面也一并删除。第三步是停止容器再提交。虽然 Docker 支持运行中 commit但停止状态能保证文件系统一致避免提交出来的镜像里出现写到一半的配置。执行docker stop bt-node docker commit bt-node bt-panel:bt1.0commit 的过程会快照整个容器的文件系统包括系统配置、宝塔面板、Nginx、站点文件。耐心等几分钟完成后用docker images查看镜像大小。如果体积还在 5G 以内属于正常范围。4.3 导出镜像并通过局域网分发镜像提交好后导出为 tar 包。常见的命令docker save -o bt-panel-bt1.0.tar bt-panel:bt1.0这个 tar 会非常大建议压一下再分发gzip bt-panel-bt1.0.tar压缩完就得到一个bt-panel-bt1.0.tar.gz。这里说一句docker save和docker export的区别。save 保存的是完整的镜像分层结构load 之后还原出来的还是一个标准镜像可以直接用来docker run而且 commit 的历史都在。export 导出的是容器文件系统的扁平 tarimport 之后得到的镜像没有分层历史也不太适合后续二次分发。我们的目标是让内网机器拿到镜像直接运行所以要用 save 加 load。分发方式上我这次用了很土但很稳的办法在存放 tar 包的目录起一个临时 HTTP 服务。cd /data/docker-images python3 -m http.server 8000内网目标机器上直接下载curl -O http://192.168.10.10:8000/bt-panel-bt1.0.tar.gz如果没有 python3也可以用scp直接传到对端或者通过局域网共享目录拷贝。小范围分发用 scp批量分发用 HTTP 更省事。我实际测试下来一个 4G 左右的 tar.gz 在千兆内网里传输大概一两分钟就完成体验完全可以接受。4.4 目标机器导入与运行镜像包传到目标机器后导入docker load -i bt-panel-bt1.0.tar.gzload 支持 gzip 压缩包会自动解压。加载完确认镜像存在docker images | grep bt-panel然后启动容器这里的端口映射必须和构建时保持一致否则面板能开但站点端口对不上docker run -itd \ --name bt-node \ --privilegedtrue \ -p 8888:8888 \ -p 8080:8080 \ -p 443:443 \ -p 888:888 \ bt-panel:bt1.0 \ /usr/sbin/init容器启动后执行docker exec -it bt-node bash进入容器确认一下宝塔的关键服务都活着ps aux | grep bt接着在浏览器访问http://目标机器IP:8888/安全入口输入构建时保存的账号密码。如果能看到面板说明镜像分发成功。再访问http://目标机器IP/如果能看到之前创建的测试站点说明宿主机 Nginx 这一层还需要在目标机器上补配置。对镜像里固化的只有容器内 Nginx宿主机 Nginx 并不在镜像里所以每台目标机器要么自己装宿主机 Nginx 做反代要么直接访问http://目标机器IP:8080看站点。这就是为什么我在前面强调“先定拓扑”。如果目标机器只想要最简环境不搞宿主机 Nginx访问 8080 端口完全够用。但如果要做统一入口每台目标机器都需要复制一份宿主机 Nginx 配置这个配置本身也可以做成一个脚本或者一个小的 Docker 容器分发下去。5. 我踩过的坑和排查速查表5.1 面板装不上的几种原因第一类是curl: command not found或者wget: command not found。基础容器里通常没有这些工具装面板脚本之前必须把 wget、curl 装上。第二类是安装脚本跑到一半报“没有足够的磁盘空间”。commit 出来的镜像如果已经导入目标机运行容器时默认使用 Docker 的存储驱动空间一旦宿主机的磁盘空间紧张安装过程可能在编译或者下载时被中断。构建前用df -h确认一下空闲空间至少留 20G 以上。第三类是脚本压根没跑起来提示安装环境校验失败。这种情况多半是容器缺少必要的内核模块或权限确认docker run时带了--privilegedtrue并且启动命令是/usr/sbin/init而不是/bin/bash。5.2 容器端口与防火墙面板打不开90% 是端口问题。排查顺序是这样先curl http://127.0.0.1:8888/安全入口在容器内测通了说明服务正常再在宿主机上 curl 宿主机 IP 的 8888通了说明端口映射正常最后在局域网其他机器上测如果只有这一步不通那就是宿主机防火墙挡了 8888 端口。内网环境常见的情况是宿主机开了 firewalld默认只放行 22、80、443 等端口。8888、8080 这些建议在构建镜像前就统一加到防火墙白名单firewall-cmd --permanent --add-port8888/tcp firewall-cmd --permanent --add-port8080/tcp firewall-cmd --reload别漏了 8080站点反代链路断了的一大原因就是宿主机防火墙把 8080 挡了但 80 是通的访问网站出现 502。5.3 commit 后开机服务起不来这是最容易踩的深坑。你 commit 的时候容器是停止状态但镜像里保存的系统服务是否能在新容器启动时自动跑起来完全取决于启动命令是不是/usr/sbin/init。如果你在目标机器上用了docker run ... bt-panel:bt1.0 bash这种方式进入容器PID 1 不是 systemd宝塔的服务不会有任何自动拉起逻辑结果就是面板打不开、Nginx 不在线。解决方式很简单目标机器启动容器时必须用和构建时一致的/usr/sbin/init。如果忘了已经用错了命令启动容器可以先把容器停了删掉再重新用正确的命令跑一次。另外还有一个隐患构建镜像时如果修改了面板的端口或者安全入口这些信息不会自动同步到其他节点。我建议在镜像里的/root/README写清楚默认端口、安全入口、账号密码、启动命令说明这样后续接手的人不用翻聊天记录找信息。5.4 数据持久化的提醒如果你打算把这个镜像用于长期运行而不是只做测试演示数据持久化必须提前规划。宝塔面板的所有数据都在/www目录包括网站源码、数据库文件、面板配置。最直接的办法是把/www挂载到宿主机目录docker run -itd \ --name bt-node \ --privilegedtrue \ -v /data/www:/www \ -p 8888:8888 \ -p 8080:8080 \ bt-panel:bt1.0 \ /usr/sbin/init这里有个大坑如果你的宿主机/data/www是一个全新的空目录把这个空目录挂载到容器的/www会直接覆盖镜像里已经配置好的全部数据相当于镜像白做了。正确做法是第一次先不用-v启动容器跑起来后把容器里的/www内容完整拷贝到宿主机目录docker cp bt-node:/www /data/www然后把容器停掉删掉再用带-v的命令重新创建容器。这样/www内容在宿主机上有一份之后再重启容器、升级镜像数据都还在。5.5 镜像太大怎么办如果做完的镜像超过 10G传输起来确实难受。瘦身的思路除了前面说的清理 yum 缓存、日志、临时文件还可以考虑只装必要的组件。这次构建我只装了 Nginx 和 PHP没有装 MySQL体积直接小了一截。如果一定要带数据库建议装 MySQL 而不是 MariaDB虽然体积差不多但有些依赖包更精简。另外不要急着把各种扩展全装上用到哪个装哪个构建镜像时的“洁癖”会在分发时变成实打实的速度优势。我个人在实际操作中的体会是这套“镜像一次建成、内网重复分发”的做法特别适合那种需要快速搭建演示环境或者多节点统一环境的团队。启动容器时用-v挂载数据目录升级时只需要替换镜像并保留挂载数据业务迁移的体验几乎可以比拟云平台的模板功能。最后再分享一个小习惯每次构建完镜像都在镜像内的/root下留一个文本说明写清楚构建日期、端口规划、安全入口、账号密码以及启动命令这样不管是自己三个月后再用还是同事接手都不会因为记忆模糊而卡壳。
阅读完成 · 觉得有帮助?
咨询建站