Portainer 容器可视化管理实战:一个界面管住几十个容器,docker.sock 安全收口与四个必踩的坑

为什么一个跑命令行的站长还需要 Portainer

如果你和我一样,服务器上跑着十几二十个容器,那你一定经历过这种时刻:半夜被监控告警叫醒,只想看一眼某个容器的日志,结果要先 docker ps -a 找到容器名,再 docker logs --tail 200,再回想上次那条 docker run 到底挂了几个端口、几个 volume。命令用熟了当然也能干活,但问题在于——记忆不是配置的可靠载体。三个月前你写的那行 docker run,三个月后你还记得清吗?

Portainer 解决的正是这个问题。它不是什么黑科技,本质就是一个跑在宿主机上的 Web 界面,通过挂载 docker.sock 拿到 Docker 的全部控制权,把「容器、镜像、网络、卷、Stack、日志、控制台」这些平时靠命令行的操作可视化出来。对个人站长来说,它的价值有三个:

  • 可视化查看状态:一台机器上几十个容器,谁在跑、谁重启过、吃多少内存,一眼扫完,不用记命令。
  • 保存编排能力:通过 Stack(本质就是 docker-compose)把服务的完整定义存下来,比手工 docker run 考古强太多。
  • 应急操作方便:手机浏览器打开就能重启容器、看日志、进控制台,出门在外也能救火。

但我要先把话说明白:Portainer 只是管理层,不能替代你对 Docker 的理解。它把 docker.sock 的权限等同于 root,配置不当等于把服务器钥匙挂在公网。这篇的重点一半在装它,另一半在安全地收口它。

前置检查:你确实需要它吗

先说结论:如果你只跑 1~3 个容器,且服务器本身有 SSH 和监控,Portainer 属于「锦上添花」,可以不上。如果你的情况符合下面任意一条,那它值得装:

  • 容器数量超过 5 个,且分散在多个 compose 项目里;
  • 有多台 VPS 需要统一管理(Portainer 支持接入远程 Docker 环境);
  • 团队/家人偶尔需要操作,但不是每个人都有 SSH 权限;
  • 你经常需要在手机上处理简单的容器重启、看日志。

部署:用 Docker 跑 Portainer,别用二进制

Portainer 官方推荐的部署方式就是它自己跑在 Docker 里。先建一个专用卷持久化它的数据,否则容器重建后账号密码、接入配置全丢:

docker volume create portainer_data

然后启动。注意这里我不用官方那条最简命令,而是加了几处加固(后面安全章节会解释为什么):

docker run -d \
  --name portainer \
  --restart=unless-stopped \
  -p 127.0.0.1:9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v portainer_data:/data \
  portainer/portainer-ce:latest

逐行说明,这里每一行都是踩过坑换来的:

  • --restart=unless-stopped:服务器重启后自动拉起。用 always 也行,但 unless-stopped 更符合直觉——你手动停了它就别自己爬起来。
  • -p 127.0.0.1:9443:9443:关键。只绑定本地回环,公网直接访问不到 9443,只能通过 SSH 隧道或 Nginx 反代进来。
  • -v /var/run/docker.sock:...:ro:把 docker.sock 挂进去是 Portainer 能工作的前提,加 :ro 是尽量的最小权限(但要知道 ro 并不等于完全安全,见下文)。
  • -v portainer_data:/data:数据持久化。

9443 是 HTTPS 端口,Portainer 自带自签名证书。首次访问会提示证书不受信任,浏览器点「继续」即可。首次进去要在 5 分钟内设置管理员账号,否则会进入「等待会话超时」状态,只能重启容器重新计时。

第一次访问与初始化

因为绑到了 127.0.0.1,本地直接开 https://127.0.0.1:9443 是不行的(那是服务器的回环)。两种进入方式:

方式一:SSH 隧道(推荐,最简单)

ssh -L 9443:127.0.0.1:9443 root@你的服务器IP

然后本地浏览器打开 https://127.0.0.1:9443,流量走 SSH 加密隧道,绝对安全。

方式二:Nginx 反代(适合长期使用,但要配合鉴权,见安全章节)

初始化时 Portainer 会问你「环境」,本地这台选 Docker Standalone,然后点 Connect 即可。它会自动发现本机的 Docker。

日常怎么用:三个我天天用的功能

1. Containers 页看全局

进去后 Containers 列表会显示每个容器的状态、镜像、端口映射、创建时间。比起 docker ps,它多了几个实用能力:

  • 点容器名进去能看 Logs(实时日志,可暂停、可搜索)和 Stats(CPU/内存/网络实时曲线);
  • Console 按钮可以直接进容器的 shell,不用 docker exec -it;
  • 每个容器都有 Recreate,改完环境变量后一键重建,不用手写 run 命令。

2. Stacks 保存服务定义

Stacks 是 Portainer 最有价值的部分。你可以把以前散落的 docker run 变成一个 compose 文件贴进去,Portainer 负责 up 和 down。比如一个 Typecho 站点:

services:
  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: 换成你的强密码
      MARIADB_DATABASE: typecho
    volumes:
      - db_data:/var/lib/mysql
  web:
    image: typecho/typecho:latest
    restart: unless-stopped
    depends_on:
      - db
    volumes:
      - web_data:/var/www/html
volumes:
  db_data:
  web_data:

存成 Stack 之后,服务的完整定义就「进版本库」了——以后迁移服务器,直接把这个 Stack 贴到新机器,不用再考古。这是我从命令行迁到 Portainer 后最大的收益。

3. Images 自动清理

Images 页能按「未使用」筛选,一键删掉那些被新版本顶替的旧镜像。注意别手贱删掉正在被容器引用的镜像(它会拒绝,但心里要有数)。

安全收口:这一步不做,前面全白搭

挂载 docker.sock 意味着谁能操作 Portainer,谁就拥有这台机器的 root。可以这样理解:控制台里点一下就能跑一个挂载了宿主机 / 的容器,然后为所欲为。所以安全收口是这套方案的生死线。

第一层:绝不让 9443 暴露公网

前面命令里已经绑了 127.0.0.1。如果你图省事绑成了 0.0.0.0:9443,建议立刻改回来:docker rm -f portainer 后用正确命令重建(数据在卷里,不丢)。

如果确实需要远程访问,用 Nginx 反代 + HTTPS + 基本认证,且只对固定 IP 开放:

server {
    listen 443 ssl http2;
    server_name portainer.你的域名;

    # 只允许你自己的家用 IP,其余一律拒绝
    allow 1.2.3.4;
    deny all;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass https://127.0.0.1:9443;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

注意 proxy_pass 用的是 https://,因为 Portainer 自己就是 HTTPS。还有 Upgrade/Connection 两行不能少,否则 Console 进不去(WebSocket 需要)。

第二层:依赖它自己的强密码

Portainer 管理员密码至少 12 位,别用生成器偷懒到 8 位。它没有两步验证(社区版),所以密码强度就是最后一道门。

第三层:定期看 Access 日志

Nginx 那层的 access.log 会记录每次登录尝试。如果你在日志里看到陌生 IP 反复访问 /api/auth,说明有人在爆破,赶紧配合 fail2ban 封掉。

四个必踩的坑

坑一:docker.sock 的 ro 并不是安全保证

很多人以为挂载时加了 :ro 就只读、安全了。事实是:Docker 的 API 本身没有只读概念,即便 socket 是只读挂载,只要能调用 POST /containers/create,你依然能创建一个特权容器去改宿主机。所以 :ro 只是「防止 Portainer 主动写 socket 文件」,不是权限隔离。真正的隔离手段是不暴露它、加反代鉴权。

坑二:容器重建后数据全没,因为忘了挂卷

如果你用的是 -v portainer_data:/data 之外的临时存储,一旦 docker rm 重建,账号和接入环境全丢,又要重新初始化。命名卷一定要建。

坑三:Stack 里的相对路径找不到

Portainer 的 Stack 默认工作目录和你在命令行跑 compose 不一样。如果你在 compose 里写了 ./conf/nginx.conf 这类相对路径挂载,很可能挂到 /data/compose/数字/ 下面,找不到文件。稳妥做法:一律用绝对路径或命名卷。

坑四:端口冲突导致起不来

如果你本机已经有服务占了 8000/9000,而 Portainer 的 Stack 又要映射这些端口,就会启动失败。先 ss -tlnp 看占用,再改端口映射。

什么时候该放弃 Portainer

说了这么多好话,也要说反面。下面这种情况我不建议用 Portainer:

  • 纯命令行爱好者:如果你已经有一套脚本化的 docker compose 工作流,Portainer 反而多一层。
  • 强调最小攻击面:多一个 Web 服务就多一个攻击面。挂 docker.sock 的组件越少越安全。
  • 多机 + 复杂编排:如果规模到了多节点编排,Portainer 的 Swarm/K8s 管理能力还不如直接用原生工具。

小结

Portainer 对个人站长的意义,不是「炫技」,而是把散落在 shell 历史里的运维知识沉淀成可视、可复用、可迁移的资产。装上它,你半夜救火时不用再翻三个月前的命令;迁移服务器时,Stack 一贴就走。但请记住它手上握着 root 权限——绑 127.0.0.1、加 SSH 隧道或 IP 白名单、用强密码,这三件事不做,其他都是给自己挖坑。

如果你已经在用 Portainer,欢迎在评论区分享你的收口方案;如果是第一次装,建议先在测试机验证一遍 Stack 迁移流程,再上生产。

Last modification:October 5th, 2026 at 10:26 pm

Leave a Comment