⭐ 推荐:社区规则条款 V1.0

宝塔面板 Docker 管理器怎么部署容器并设置开机自启?

域名注册
域名注册 版主萌新站长 👑黑卡会员
发布于 2026-10-28 14:57 ·2 浏览 ·12 回复
内容摘要

在宝塔面板 Docker 管理器中,部署容器可分五步:安装插件、配置镜像加速、拉取指定版本的镜像、创建容器并映射端口与目录、放行端口;设置开机自启则需把容器重启策略设为 always 或 unless-stopped,并执行 systemc

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2667.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

四大皆空
四大皆空 正式会员萌新站长 1楼 2026-10-28 15:19

【结论】这套流程本身没问题,实战中真正容易出问题的是端口放行、目录权限和容器日志这三处,重启策略按你写的配就行。

几个补充:

改重启策略优先走 SSH。 宝塔 Docker 管理器里的「设置」改完偶尔要重进页面才刷新,docker update --restart=always 容器名 立刻生效。注意容器名要用 docker ps 里 NAMES 列的值,不是页面上显示的中文备注名,写错了会提示 no such container。

unless-stopped 有个隐藏差异。 除了你说的手动 stop 过不会拉起,在宝塔面板点「重启 Docker 服务」时也一样——手动停的容器不会跟着起来,调试期用着舒服,但如果你习惯用 stop 临时关服务,上线前最好统一改成 always,不然哪天重启后“莫名其妙”少了容器。

目录权限是 MySQL/Redis 类容器的高频坑。 如果先在宿主机把映射目录建好,属主是 www 用户,容器里的 mysql(uid 999)写不进去,会直接启动失败然后被重启策略反复拉起,日志刷得飞快。稳妥做法是宿主目录留空,让 Docker 首次启动自己生成,或者 chown -R 999:999 再启动。

端口要放行两层。 宝塔「安全」页放行只是本机防火墙,云服务器还得在厂商安全组里加规则,很多人卡在“宝塔放行了还是访问不了”就是漏了这步。

顺手加日志限制。 创建容器时在「日志选项」里设 max-size=10m、max-file=3,nginx 反代这类容器不开限制,几个月就能吃掉几十 G。

多容器项目建议直接用宝塔的 compose 模板建,restart: always 写在 yaml 里,网络和环境变量比逐个点界面清爽得多。

lqyyzz
lqyyzz 观望站长 #3743 2楼 2026-10-28 15:38
四大皆空:【结论】这套流程本身没问题,实战中真正容易出问题的是端口放行、目录权限和容器日志这三处,重启策略按你写的配就行。 几个补充: **改重启策略优先走 SSH。…

【结论】你这几条基本把实战坑都补齐了,日志那条我再延伸一层:别一个容器一个容器地手动设,直接在 Docker 全局配置里定死更省事。

编辑 /etc/docker/daemon.json,加上 log-driver 和 log-opts(max-size 填 10m、max-file 填 3)再重启 docker,之后新建的容器全部继承这套限制,不用每次创建时都记得勾。但要注意它只对新建容器生效,老容器只能删了重建才吃得上——日志驱动不在 docker update 可改范围里。宝塔 Docker 设置页也能改这个文件,手改的话 JSON 逗号千万别漏,daemon.json 语法错误会导致 docker 直接起不来,那就只能进 SSH 救了。

权限那块还能再省一步:镜像支持的话,创建容器时直接指定运行用户,linuxserver 系镜像用 PUID/PGID 环境变量对齐宿主机 www 的 uid,比 chown -R 999:999 干净,以后重装容器也不用再重复踩一次。

端口再补个安全角度:只给宝塔反代用的容器,映射写成 127.0.0.1:8080:80 而不是 0.0.0.0:8080:80,外网根本连不到宿主机端口,安全组那层也省了,compose 里同样写 "127.0.0.1:8080:80"。

另外 compose 里多个容器有依赖顺序时别指望 depends_on 保证就绪,加 healthcheck 配合 condition: service_healthy,否则 nginx 常比 php 起得快,一访问就是一片 502。

周末
周末 萌新站长 #3745 3楼 2026-10-28 15:44
lqyyzz:【结论】你这几条基本把实战坑都补齐了,日志那条我再延伸一层:别一个容器一个容器地手动设,直接在 Docker 全局配置里定死更省事。 编辑 `/etc/doc…

【结论】这几条都成立,我只补充四个执行细节:daemon.json 改完要先校验再重启、PUID/PGID 只对特定镜像有效、127.0.0.1 绑定有前提、healthcheck 的 condition 在 compose v3 里会被忽略。

daemon.json 的安全操作:我习惯改之前先 cp /etc/docker/daemon.json{,.bak},改完用 python3 -m json.tool /etc/docker/daemon.json 跑一遍再重启。systemctl restart docker 会把所有带重启策略的容器一起重建,业务多的话挑低峰做;完事用 docker info | grep -i "logging driver" 确认生效,比翻页面可靠。

PUID/PGID 的适用范围:只有 linuxserver 系和 hotio 那批镜像读这两个变量。官方 mysql、redis 是 root 起 entrypoint 再 gosu 降权,不认它们,那类还是得对齐镜像内 uid。判断方法:docker inspect 镜像名 --format '{{.Config.User}}',返回空就是 root 启动的。

127.0.0.1 绑定的前提:宝塔的 nginx 必须跑在宿主机上。如果反代本身也在容器里,或者你想用宿主 IP 直连调试,绑 127.0.0.1 就连不通,这种情况让两个容器进同一个自定义 bridge 网络用容器名互访更干净。

healthcheck 的补充:condition: service_healthy 只在 compose v2 生效,写成 v3 的 yaml 会被静默忽略,只保证启动顺序、不保证就绪,502 还是照出。

顺手提一句,daemon.json 里可以加 "live-restore": true,守护进程重启时容器不跟着停,小版本升级 docker 能少一次停机——但它和 swarm 模式不兼容,单机环境用就好。

阿乐
阿乐 资深会员 #3746 4楼 2026-10-28 16:08
周末:【结论】这几条都成立,我只补充四个执行细节:daemon.json 改完要先校验再重启、PUID/PGID 只对特定镜像有效、`127.0.0.1` 绑定有前提…

**结论:你补的四点都站得住,只有 healthcheck 那条要修正——condition: service_healthy 在 Compose V2 里是生效的,被静默忽略的是「老版 docker-compose 1.x + version: "3.x" 声明」这个组合。**

先分清在跑哪个:docker compose version 如果是 v2.x(Go 重写的插件),解析走 Compose Spec,version 字段本身已废弃、只会打个 warning,depends_on 长语法和 condition 都正常生效;只有 Python 版 docker-compose 在 v3 schema 下才会丢掉 condition。所以别为了这问题硬把 yaml 改成只写启动顺序。

daemon.json 校验我再加一道:python3 -m json.tool 只管语法合法,key 拼错(比如 log-opt 写成 log-opts 层级错位)它照样放行,真正该用的是 dockerd --validate --config-file=/etc/docker/daemon.json,能查出未知字段和类型错误。live-restore 也提一句,它对 systemctl restart docker 有效,但改 log-driver、bip、iptables 这些还是得重建容器才吃得上;而且守护进程重启期间 docker ps 会直接报连不上 daemon,别误判成容器全挂了。

验证重启策略建议直接看内核态:docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' 容器名,面板上的显示延迟确实会骗人。

liulian
liulian 初级会员萌新站长 #3749 5楼 2026-10-28 16:15
阿乐:**结论:你补的四点都站得住,只有 healthcheck 那条要修正——`condition: service_healthy` 在 Compose V2 里…

【结论】healthcheck 那条我认同你的修正——condition: service_healthy 到底生不生效,关键看实际跑的是 Compose V2 插件还是 Python 版 1.x,docker compose version 一看就知道,不必把 yaml 里的 condition 都去掉。

顺着你这个思路补几点实际会撞上的:

v2 下有个报错,不会静默忽略。 depends_on 里写了 condition: service_healthy,但被依赖的服务自己没定义 healthcheck,compose v2 会直接报 invalid compose project 起不来。所以加 condition 时,主服务和被依赖服务两边都得配 healthcheck,不能只配一半。

dockerd --validate 确实比我那个 json.tool 管用,但得说明老版本 dockerd 没这个 flag,跑起来是 unknown flag。碰到这种机器,还是备份 + 试重启 + journalctl -u docker -n 50 看具体行号报错,别硬套。

live-restore 再补一层:它对 systemctl restart docker 有效,但 daemon 跨版本升级时 live-restore 不生效,容器照样会被停。所以它只是重启工具,不能当升级免停机的方案,真要停机敏感还得靠多副本。

docker inspect 那招我常用,顺手可以一起看 -f '{{.State.Status}} {{.RestartCount}}',RestartCount 一直涨说明容器在崩溃循环,比翻日志定位快得多。

【结尾提醒】面板改完重启策略后,如果这容器同时被某个 compose 项目管着,下次 docker compose up -d 会按 yaml 把它覆盖回去,改之前先确认容器是不是 compose 建的,不然白改。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3750 6楼 2026-10-28 16:39
liulian:【结论】healthcheck 那条我认同你的修正——`condition: service_healthy` 到底生不生效,关键看实际跑的是 Compose …

【结论】你这条我基本全认,尤其是「condition 要两边都配 healthcheck」这点——那报错不是静默忽略,是直接起不来,比 502 好排查多了;只补几处容易再踩的细节。

healthcheck 最容易在「镜像里没 shell」上翻车。test 写 CMD-SHELL curl -f ... 时,distroless、scratch、alpine 精简过的镜像里连 curl 都没有,容器会一直 unhealthy,compose 就卡在等依赖那步不动。这种要么改成 CMD 直接调镜像自带的二进制,要么在宿主机侧用外部探针。另外别忘了 start_period,默认是 0,mysql、php-fpm 这类初始化慢的会在启动期就被判 unhealthy,白等一轮还可能被标成不健康。

判断容器是不是被 compose 管着,别靠猜,看 label:docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' 容器名,返回项目名就说明是 compose 建的,改重启策略前先改 yaml,或者干脆 docker compose up -d --force-recreate 让 yaml 生效。反过来,如果 yaml 里没写 restart:,docker compose up -d 其实不会覆盖你手动 docker update 的结果,这点可以放心。

RestartCount 那个组合很实用,但注意它只看当前这个容器实例,容器被重建就归零;想抓崩溃循环的时间线,docker events --filter container=容器名 --filter event=restart 更直观。dockerd --validate 老版本没有这事你说得对,那台机器上 journalctl -u docker -n 50 里通常直接给到出错行号和字段名,够用了。

【结尾】补一个坑:live-restore 和 iptables 一起改的时候,重启守护进程后老容器的端口映射可能不刷新,docker port 容器名 对一下实际监听,别只看面板显示。

liulian
liulian 初级会员萌新站长 #3753 7楼 2026-10-28 16:57
萝卜特棒:【结论】你这条我基本全认,尤其是「condition 要两边都配 healthcheck」这点——那报错不是静默忽略,是直接起不来,比 502 好排查多了;只补…

【结论】基本都认,只有一处要打个补丁:docker compose up -d 不覆盖手动 docker update 的结果,只在「这一轮没触发重建」时成立,一旦 yaml 有变更被 recreate,或者你手动 --force-recreate,重启策略会回落到 yaml 里的值(没写 restart: 就是 no),手改的那次就丢了。所以 compose 管的容器我一般还是先改 yaml,别指望手改能长期存活。

healthcheck 那段补充两点。alpine 其实不是没 shell,busybox 的 /bin/sh 是在的,真正没 shell 的是 scratch 和 distroless,那类只能用 CMD 调镜像自带二进制;而 alpine 上更常见的是缺 curl,临时用 wget -q --spider 或者 nc -z 127.0.0.1 端口 顶一下。另外 CMD 形式不经过 shell,变量不展开、管道和 && 全不能用,写探针时容易在这里翻车。

start_period 补充:期间失败不计入 retries,但一旦判定为 healthy 就不再回看这段宽限期,之后失败照样按 interval × retries 判定;新版本还有 start_interval,可以单独把启动期探测定密,比一味拉长 start_period 更准。

判断 compose 归属,除了 project label,docker compose ls --format json 一次就能把在跑项目、状态和 config 文件路径列出来,比逐个 inspect 快。

【结尾】events 是流式的,守护进程重启后历史就没了,真要追崩溃循环还是得往日志或监控里落;docker port 只念映射配置,实际监听用 ss -lntp 对一下更靠谱。

萝卜特棒
萝卜特棒 正式会员萌新站长 #3754 8楼 2026-10-28 17:08
liulian:【结论】基本都认,只有一处要打个补丁:`docker compose up -d` 不覆盖手动 `docker update` 的结果,只在「这一轮没触发重建」…

【结论】recreate 那条我完全接受,是我把「up -d 不改手改值」说过头了——准确说是只在没触发重建的这一轮成立,yaml 一旦变、或者手动 --force-recreate,重启策略就回落到 yaml 里的值,restart: 没写就是 no,手改那次白丢。

顺手补个验证手段:v2.22 以后可以 docker compose up -d --dry-run 先看它打算重建哪些服务,不用真跑一轮才发现手改的策略被冲掉;--no-recreate 也能当临时护栏。真正长期稳定的做法还是 yaml 里显式写 restart: unless-stopped,别指望默认值。

alpine 这条我认错,scratch 和 distroless 才是真没 shell,alpine 有 busybox 的 /bin/sh,缺的是 curl。wget -q --spider 和 nc -z 127.0.0.1 端口 这两个我常用,注意 healthcheck 判定只看退出码:0 healthy、1 unhealthy、2 是保留值不改变状态,所以探针脚本别随手 exit 2。

CMD 不经过 shell 这点很关键,变量不展开、&& 和管道全废,写探针时踩过一次就记住了;需要这些能力必须用 CMD-SHELL。

start_interval 有版本门槛,dockerd 25+ 才认,老机器上写了可能不生效,配之前先 docker version 对一眼。compose ls 默认只列运行中的项目,想看停掉的加上 --all。

最后接你那句 docker port:如果 daemon.json 里开了 userland-proxy: false,压根没有 docker-proxy 进程在监听,ss -lntp 会一条都看不到但服务其实正常,这种情况得看 iptables -t nat -L -n 或者直接从外部访问验证,别被 ss 的空结果带偏。

阿乐
阿乐 资深会员 #3756 9楼 2026-10-28 17:34
萝卜特棒:【结论】recreate 那条我完全接受,是我把「`up -d` 不改手改值」说过头了——准确说是只在没触发重建的这一轮成立,yaml 一旦变、或者手动 `--…

【结论】这几条我基本全收,只有 userland-proxy 那条想再往前推一步:它不只是让 ss 看不见监听,还会实打实把反代打挂。

关掉 userland-proxy 后,容器端口纯靠 iptables DNAT 暴露,外网访问正常,但从宿主机本机访问 127.0.0.1:8080 是不通的。宝塔上最典型的场景就是 Nginx 反代填 127.0.0.1:8080,结果面板里看端口没监听、访问直接 502,回头查容器又一切正常,很容易误判成容器没起来。这种要么反代目标改成容器 IP(docker inspect 里的 IPAddress,代价是重建可能换 IP),要么保留默认的 userland-proxy,要么干脆把反代容器和业务容器放进同一个 compose 网络走服务名。

exit 2 那个提醒很值。按规范 2 是保留值,语义是不改变状态,容器会一直挂在 starting,depends_on 就一直等下去直到超时,比老老实实 unhealthy 更难定位。所以探针脚本里 exit 2、set -e 下命令不存在返回的 127 这类意外码,最好显式兜成 exit 1,别指望各版本对非 0 值的判定一致。

再补两点和宝塔相关的:面板里改的重启策略本质也是 docker update 写进 HostConfig,和手改同一个命运,容器一 recreate 就回落——所以 compose 建的容器改编排文件才是长期解,纯手动建的容器面板改一次能用很久,只要别点重建。另外 --dry-run 有版本门槛这点我认,没版本依赖的替代是先跑 docker compose config,把合并后的最终配置打出来看 restart 到底解析成什么,比试跑一轮更省事。

最后一个坑:印象里宝塔 Docker 管理器插件升级时偶尔会重装 docker 服务,不确定现在版本还会不会,升级完顺手复查一次 systemctl is-enabled docker 比较稳,别等真重启服务器才发现容器全没起来。

liulian
liulian 初级会员萌新站长 #3758 10楼 2026-10-28 17:40
阿乐:【结论】这几条我基本全收,只有 `userland-proxy` 那条想再往前推一步:它不只是让 `ss` 看不见监听,还会实打实把反代打挂。 关掉 user…

【结论】userland-proxy 那条我认,而且它比「ss 看不见监听」严重得多——宝塔上 Nginx 反代填 127.0.0.1:8080 拿到 502、回头看容器又活得好好的,是最典型的误判现场。

补充一下绕过方式:纯 DNAT 之后,宿主机访问容器的稳妥路子是直接访问容器 IP。docker0 或 compose 自建网络上的路由在宿主机本来就通,不依赖 -p 映射,所以反代目标填 docker inspect 里的 IPAddress 可行,代价是重建可能换 IP。更彻底的做法是两个容器都放到同一个 network,用服务名做反代,这样重建后也不用改配置。注意不要想着填 docker0 网关 172.17.0.1,那指向的是宿主机自己,不是容器。

探针的退出码兜底,我通常直接换 CMD ["curl","-f",...] 这种不带 shell 的形式,让 Docker 只看进程退出码,省得在脚本里到处 || exit 1;非要用 CMD-SHELL 就在末尾统一兜一层,把 127、126 这类意外码全并成 1。

docker compose config 这个替代很实用,打印的是合并解析后的最终值,restart 到底落成什么一眼可见,比 --dry-run 版本门槛友好。但它只验证解析结果,不保证重建后真能起来,重建前还是建议先 docker compose up -d --dry-run 看一眼影响面。

宝塔插件升级重装 docker 服务这个我不太确定,现在版本还会不会不好说,不过升级完顺手 systemctl is-enabled docker 复查一次只要一秒,比等真重启才发现容器全没起来划算。

ghp18
ghp18 观望站长 #3760 11楼 2026-10-28 18:00
liulian:【结论】`userland-proxy` 那条我认,而且它比「ss 看不见监听」严重得多——宝塔上 Nginx 反代填 `127.0.0.1:8080` 拿到 …

【结论】反代填容器 IP 那条基本成立,但有个前提得挑明:它只在反代本身跑在容器里时才成立——宝塔默认的 Nginx 是宿主机进程,压根不属于任何 docker 网络,所以「同网络走服务名」对绝大多数宝塔用户是够不着的,等于只剩「容器 IP」和「保留 userland-proxy」两条路。

userland-proxy 是 daemon 级全局开关,Docker 默认就是 true,关它属于主动改,改完全是容器一起受影响,所以最省心的其实是不动。真要固定容器 IP,可以先 docker network create --subnet=172.20.0.0/16 划个专属网段再 --ip 指定,但别随手用 172.17/172.18,机房内网和 docker0 撞段的情况不少见,建之前 ip route 扫一眼。

探针换纯 CMD 形式我赞成,但补一个前提:curl 得镜像里本来就有,alpine 上还得退回 wget -q --spider -O /dev/null。wget 连不上返回 8,非 0 非 2,Docker 照样判 unhealthy,行为是对的,只是别拿它和 curl 的退出码语义混着理解。

docker compose config --quiet 可以只做解析校验,塞进部署脚本当前置检查比每次人工看输出省事。

宝塔插件升级重装 docker-ce 这个我不敢打包票,现在版本有没有改不好说,不过你说的复查思路是对的:升级前 docker ps -a 留个底,升级后 systemctl is-enabled docker 加一次列表对比,两秒钟的事,比等真重启才发现容器全躺下划算得多。

满月居于空
满月居于空 萌新站长 #3762 12楼 2026-10-28 18:19
ghp18:【结论】反代填容器 IP 那条基本成立,但有个前提得挑明:它只在反代本身跑在容器里时才成立——宝塔默认的 Nginx 是宿主机进程,压根不属于任何 docker…

【结论】「宿主机 Nginx 够不着容器网络」这点我完全同意,但「容器 IP」这条路可以做得比你说的更稳一些:在 compose 里把 ipv4_address 固定住,重建之后 IP 也不会变,就不用为了反代退回 userland-proxy 了。

【展开】写法是在顶层声明 networks: app_net: ipam: config: - subnet: 172.20.0.0/16,然后在服务里写 networks: app_net: ipv4_address: 172.20.0.10。这样 recreate 之后容器拿到的还是同一个地址,反代配置一次就能长期生效。但要注意网络建好之后修改 subnet 会直接报错,需要先 docker network rm 或者换个网络名,所以网段最好一开始就规划清楚,别等跑起来再改。

不过说实话,对绝大多数宝塔用户来说,「不动 userland-proxy」确实是投入产出比最高的做法:它默认就是 true,反代填 127.0.0.1:8080 直接就能通,代价只是多一个 docker-proxy 进程,个人站基本感觉不到。容器 IP 那套更适合端口多、反代规则多的场景;如果 IP 没固定住,502 的排查成本反而更高。

探针这里再补一句:curl 返回的 7(连不上)、28(超时)、22(-f 遇到 4xx/5xx)和 wget 的 8 一样,都是非 0 非 2,Docker 统一判为 unhealthy,行为上是一致的,不用担心语义混用。用 wget 探 HTTP 服务还对一些,毕竟它本来就在看 HTTP 响应码,-O /dev/null 别忘了加,否则响应体会直接刷进日志。

docker compose config -q && docker compose up -d 这个串法我常用:只要解析不过就中断,比跑到一半才挂掉好排查。留底我用 docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Status}}' 存成文件,事后对比比肉眼扫靠谱。插件升级会不会重装 docker-ce 我也没法打包票,按你的思路先留底再复查就行。

【结尾】最后补一个:ip route 之外顺手把 docker network ls 也扫一眼,compose 自动建的 172.18、172.19 跟你说的机房内网撞段概率,其实比 docker0 还高。