中文 English

我把 Docker 命令行装进了“驾驶舱”:Portainer 2.39.4 从部署到避坑,一篇就够

发布时间: 2026-07-10
Portainer Docker Docker Compose 容器 自托管 运维 Ubuntu Windows macOS

先说结论

Portainer 不是 Docker 的替代品,它更像 Docker 主机的“驾驶舱”:底层发动机仍然是 Docker Engine,Portainer 只是把容器、镜像、网络、卷和 Compose Stack 整理成网页按钮、表格与状态卡片。

我用 portainer/portainer-ce:2.39.4 做了一次隔离部署,完成初始化、接入本机 Docker、查看仪表盘、筛选容器、创建演示 Stack,并记录了真实截图。部署本身只要一条 docker run,真正需要认真理解的却是三件事:/data 必须持久化、/var/run/docker.sock 权限非常高、生产环境不要把管理页面毫无遮挡地暴露到公网。

本文没有展示完整 IP 地址、真实主机名、内网域名、管理员密码、Token、Cookie、私有镜像地址或生产容器名称。截图里的实验资源使用专门的演示名称,容器地址已遮盖。

原创题图:把容器、镜像、网络和存储集中到一个自托管控制台

为什么很多人装了 Docker,最后还是会装 Portainer

Docker 命令并不难。查看容器用 docker ps,看日志用 docker logs,更新 Compose 项目用 docker compose up -d。问题在于,当机器上只有两三个容器时,这些命令像抽屉里三把钥匙,随手就能找到;当容器、网络、卷、镜像和 Stack 越来越多时,钥匙开始装满一整面墙。

Portainer 的价值不是让人“再也不用命令行”,而是把常见状态集中起来:哪个容器正在运行、哪个停止了、映射了什么端口、属于哪个 Stack、用了哪个镜像、挂载了哪个卷。遇到故障时,你仍然需要理解 Docker,但不必先在一堆命令输出里找入口。

可以把一台 Docker 主机想成一栋公寓楼:

但控制室不是玩具。谁拿到控制室钥匙,谁通常就能操作整栋楼。这也是本文后面反复强调 Docker Socket 安全性的原因。

架构图:浏览器经过 Portainer,再通过 Docker Socket 管理 Docker Engine

我这次实际验证了什么

这次没有改动长期运行的 Portainer,而是单独启动了一个 2.39.4 LTS 临时实例,使用独立容器名、独立数据目录和不冲突的测试端口。随后完成了这些操作:

  1. 拉取固定版本镜像,并核对镜像摘要与实际版本。
  2. 持久化 /data,挂载 Docker Socket。
  3. 初始化管理员账号并建立本机 Docker Environment。
  4. 创建一个只包含演示容器、演示网络和演示卷的 Compose Stack。
  5. 在 Containers 页面只筛选演示资源。
  6. 在 Stacks 页面确认 Compose Stack 处于可管理状态。
  7. 完成截图后删除临时 Portainer、演示 Stack、演示卷、演示网络和测试数据目录。

下面这张是真实登录页面。页面截图不包含浏览器地址栏,因此不会带出访问地址。

真实截图:Portainer CE 2.39.4 登录页面

最短部署命令:能跑,但先看懂每一行

题目给出的部署方法如下:

docker run -idt \
  -p 9000:9000 \
  --name=portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /docker/portainer_data:/data \
  portainer/portainer-ce:2.39.4

它做了五件事:

这条命令可以工作,但我更推荐把 HTTPS 端口作为默认入口:

docker run -d \
  -p 9443:9443 \
  --name portainer \
  --restart=always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /docker/portainer_data:/data \
  portainer/portainer-ce:2.39.4

首次访问时,Portainer 会使用自签名证书,所以浏览器可能提示证书不受信任。自签名证书不等于流量没有加密,只是浏览器无法确认签发者身份。正式环境最好在反向代理或 Portainer 上配置可信证书,并限制访问来源。

如果你确实需要兼容旧入口,可以同时映射两个端口:

-p 9443:9443 -p 9000:9000

但不要因为 9000 打开方便,就把它直接暴露到整个互联网。管理面板不是普通博客页面,它后面连着 Docker 的控制权。

第一次登录:不要错过初始化窗口

启动后访问:

https://SERVER_ADDRESS:9443

页面会要求建立管理员账号。密码应当使用独立、高强度、没有在其他网站复用的值。Portainer 新版初始化还引入了 setup token 保护:在我实际调用初始化 API 时,如果没有带启动日志里的一次性 X-Setup-Token,服务器会返回 403。正常网页初始化会处理相应流程;如果实例在等待初始化时停留太久并提示超时,最简单的处理通常是重启尚未配置的 Portainer,再立即完成初始化。

完成登录后,Home 页面会列出可管理的 Environment。我的临时实验环境显示为 Local Docker Lab。真实仪表盘会汇总 Stack、Container、Image、Volume 和 Network 数量。

真实截图:Portainer Dashboard 汇总 Docker 资源状态

Docker Socket:最方便的一行,也是最危险的一行

-v /var/run/docker.sock:/var/run/docker.sock

很多教程把它当作普通目录挂载一笔带过。实际上,Docker Socket 更像“物业总钥匙接口”。Portainer 通过它创建容器、挂载目录、连接网络、读取日志、执行命令。能控制 Docker 的管理员通常也能间接获得宿主机上的高权限能力。

因此应该遵守这些原则:

  1. 不把 Portainer 管理端口直接暴露给不可信网络。
  2. 优先通过 VPN、可信反向代理、访问控制或防火墙进入。
  3. 管理员账号不要多人共用;有团队需求时再设计用户、组和权限。
  4. 不安装来源不明的 Extension、模板或 Stack。
  5. 定期备份 /data,但备份文件同样要按敏感数据保护。
  6. 不要为了“看起来安全”把 Socket 改成只读挂载;Portainer 如果要管理容器,本来就需要写操作。真正的安全边界是访问控制和环境隔离。

Portainer 让高权限操作更容易,并不会让高权限本身消失。就像把总闸做成触摸屏,界面更漂亮,但按错按钮仍然会停电。

日常怎么用:先从 Containers、Stacks 和 Volumes 开始

1. Containers:看状态、日志和资源归属

Containers 页面适合做第一轮检查:容器是否运行、属于哪个 Stack、使用哪个镜像、创建时间、端口和快速操作。截图中我用搜索框只保留了实验容器,并把容器内部地址替换成 [redacted]

真实截图:筛选后的演示容器列表,内部地址已遮盖

遇到服务打不开时,可以按这个顺序排查:

  1. 容器是不是 running
  2. Logs 里有没有启动失败、权限错误或端口占用。
  3. Inspect 里的挂载路径是否正确。
  4. Published Ports 是否与预期一致。
  5. 容器是不是由 Stack 管理;如果是,优先修改 Stack,而不是只改一次性容器参数。

2. Stacks:把一组服务当成一个项目管理

Stack 可以理解成“一张整套装修清单”。一个 Compose 文件可以同时定义应用、数据库、网络、卷和环境变量。相比手动创建多个容器,Stack 更容易复现,也更适合版本管理。

实验中我创建了 portainer-blog-demo,里面只有一个无业务数据的演示容器、一个网络和一个卷。页面能够识别它是 Compose Stack,并记录创建时间与控制方式。

真实截图:Portainer 中的 Compose Stack 列表

创建 Stack 时,敏感值不要直接写进公开文章、截图或 Git 仓库。可以使用 Portainer 的环境变量输入、受控配置文件或外部 Secret 管理方案。即使页面把密码显示成圆点,也要确认浏览器开发者工具、Stack 内容和备份中是否仍然保存明文。

3. Volumes:删容器前先问“数据住在哪里”

删除容器不一定删除 Volume,但勾选错误选项或运行清理命令可能把数据一起带走。操作数据库、相册、文档或配置中心之前,先到 Volumes 页面确认:

容器像可替换的房间,Volume 才像储物间。装修模板可以重新下载,家庭相册不能靠重新拉镜像恢复。

三个平台的一键脚本

我为本文准备了三套安装器。它们只依赖本机 Docker,不调用额外控制平台,不上传配置,不自动发现或打印局域网地址。默认固定 2.39.4,默认只开放 9443,只有显式开启时才映射传统 9000

Windows 11

前提是 Docker Desktop 正在使用 Linux Containers:

Set-ExecutionPolicy -Scope Process Bypass
.\install-portainer-windows11.ps1

如果确实要同时启用传统 HTTP:

.\install-portainer-windows11.ps1 -EnableLegacyHttp

Windows 和 macOS 默认使用 Docker named volume 保存 /data,避免桌面系统的文件共享路径差异。

Ubuntu 26.04

chmod +x install-portainer-ubuntu2604.sh
./install-portainer-ubuntu2604.sh

指定其他数据目录:

./install-portainer-ubuntu2604.sh \
  --data-dir /srv/portainer/data \
  --enable-legacy-http

脚本会先尝试当前用户连接 Docker;如果需要且系统存在 sudo,会改用 sudo docker。已有同名容器时不会直接删除,而是检查状态并启动停止的容器,避免把原配置当成垃圾重建。

macOS 26

chmod +x install-portainer-macos26.zsh
./install-portainer-macos26.zsh

指定 Volume 名称:

./install-portainer-macos26.zsh --volume portainer_data_main

Docker Desktop 没有启动时,脚本会直接报错,不会继续执行一半留下难以判断的状态。

人工自动执行:你自己操作时的推荐顺序

所谓“人工自动执行”,不是逐字符手敲所有命令,而是由人确认环境和风险,再让脚本完成重复动作。建议按下面顺序:

  1. 运行 docker ps -a,确认没有同名 portainer 容器。
  2. 检查 9443 是否已被其他服务占用。
  3. 确定 /data 使用 bind mount 还是 named volume。
  4. 下载对应系统脚本,先阅读参数和数据路径。
  5. 执行脚本并等待它报告容器进入 running 状态。
  6. 打开 https://localhost:9443 或通过受控网络访问服务器地址。
  7. 立即完成管理员初始化。
  8. 登录后检查 Environment、Containers、Volumes 和 Stacks。
  9. 设置防火墙、VPN 或反向代理访问策略。
  10. 做一次 /data 备份,并记录恢复步骤。

不要把“页面能打开”当成部署完成。页面打开只证明 Web 进程活着,不证明数据目录持久化正确,也不证明重启后还能恢复。

Agent 自动配置:可以直接复制的任务说明

如果你让 Codex、Claude Code 或其他运维 Agent 执行,可以给它下面的约束,而不是只说“帮我装 Portainer”:

请部署 Portainer CE 2.39.4,并遵守以下要求:
1. 先只读检查 Docker 版本、同名容器、9443/9000 端口占用和现有数据目录。
2. 不允许删除任何不是本次任务创建的容器、卷、网络或目录。
3. 默认只开放 9443;除非我明确要求,否则不要开放 9000。
4. Linux 使用指定的持久化目录,Windows/macOS 使用 Docker named volume。
5. 必须固定镜像版本,不能使用 latest。
6. 启动后核对容器状态、镜像标签、restart policy、挂载和 HTTPS 响应。
7. 不要在日志和报告中输出完整 IP、主机名、密码、setup token、JWT、Cookie 或私有域名。
8. 如果需要截图,只展示实验资源,并遮盖容器内部地址和无关资源名。
9. 如果发现已有 Portainer,停止部署并报告,不允许直接覆盖。
10. 最终给出部署结果、访问方式、备份路径、回滚方法和清理范围。

Agent 最容易犯的错不是命令写错,而是“看到同名资源就顺手删除”。因此一定要明确:只清理本次任务创建的对象。自动化像请搬家公司,清单写得越清楚,越不容易把邻居家的箱子一起搬走。

备份、升级与回滚

使用 bind mount 时,最直观的备份方式是先停止 Portainer,再复制数据目录:

docker stop portainer
tar -C /docker -czf portainer-data-backup.tar.gz portainer_data
docker start portainer

备份文件应放到受保护位置,并限制读取权限。里面可能包含用户、Environment、Endpoint、Stack 元数据和其他敏感设置。

升级前推荐:

  1. 查看目标版本发布说明与已知问题。
  2. 备份 /data
  3. 记录当前镜像标签和 docker inspect 结果。
  4. 停止并删除旧容器,但保留数据目录或 Volume。
  5. 使用新版本镜像按原参数重建。
  6. 验证登录、Environment、Stack、容器操作和审计日志。
  7. 如果异常,按原标签重建并挂回原数据。

不要直接把固定版本改成 latest 后期待“一劳永逸”。升级像给物业控制室换整套设备:数据档案要留着,旧设备型号要记着,新设备要逐项验收。

流程图:固定版本、持久化、HTTPS、访问限制、备份、升级验证

常见问题与根因

页面打不开,但容器显示 running

先检查端口映射和监听:

docker ps --filter name=portainer
docker logs --tail 100 portainer

常见根因包括端口已占用、防火墙未放行、访问了错误协议、反向代理没有转发 WebSocket,或者容器刚启动还在生成证书。

重建后像全新安装,账号和 Stack 都不见了

根因通常是 /data 没有持久化,或者重建时挂载到了另一个空目录。检查:

docker inspect portainer

重点看 /data 对应的 Source。不要只看容器名字一样就以为数据一定一样。

为什么 Portainer 能看见整台 Docker 主机的容器

因为挂载了 Docker Socket。它不是扫描网络猜出来的,而是直接通过 Docker API 读取和操作资源。方便与风险来自同一个入口。

初始化提示超时怎么办

对尚未配置、没有业务数据的新实例,可以重启容器后立即完成初始化。2.39.4 的启动日志还会生成一次性 setup token,用来保护管理员初始化和未初始化状态下的恢复接口。不要把这个 token 发到聊天、工单或博客截图里。

忘记管理员密码怎么办

先备份 /data,再按 Portainer 官方密码重置流程操作。不要在没有备份的情况下删除数据目录“重新安装”,那会把 Environment 和 Stack 元数据一起清空。

能不能直接暴露公网

技术上可以,运维上不推荐裸奔。至少应有 HTTPS、强密码、访问来源限制、及时更新和备份。更稳妥的入口是 VPN、零信任访问网关或只允许管理网段进入的反向代理。

Portainer 能完全替代命令行吗

不能,也没必要。Portainer 适合看全局、做常见操作、管理 Stack 和帮助团队协作;命令行适合批处理、脚本化、精确排障和紧急恢复。最好的状态不是二选一,而是知道什么时候用仪表盘,什么时候打开终端。

最后:一条命令安装,只是故事的开头

Portainer 最吸引人的地方确实是一条命令就能启动。但如果文章只停在“浏览器能打开”,就漏掉了真正重要的部分:数据放在哪里、谁能访问管理页、Docker Socket 代表什么、升级前如何备份、出了问题怎样回滚。

这次隔离实验让我再次确认:Portainer 不是把运维变成点点鼠标,而是把 Docker 的状态和操作集中到一个更容易理解的控制室。控制室能降低日常操作门槛,却不能替代安全边界、备份和 Docker 基础知识。

把它当作驾驶舱,你会同时关心仪表、权限、黑匣子和应急手册;把它只当作“好看的 Docker 网页”,迟早会在一次错误删除或公网暴露后补课。

参考资料