资讯动态

给Docker容器配置SSH登录:从安装到密钥认证的完整实践

发布时间:2026/10/5 11:03:35 来源:尧图企业网站定制
说个实际场景你在自己的 Ubuntu 机器上装好了 Docker某个容器跑起来了。平时想进去敲命令docker exec -it 容器名 bash 也挺顺手但有一天你在外面想远程连回这台机器上的某个容器里看一眼日志、改个配置或者你希望把容器当成一个独立的小服务器来管理这时候给 docker 容器设置 ssh 登录就成了刚需。这篇我就以 Ubuntu 镜像为例把整个配置过程、背后的原理、自启动方案、密钥登录和踩坑记录一次讲透。内容偏实操适合刚接触 Docker 不久、又想摆脱“每次都得先登录宿主机再进容器”这种别扭流程的人。1. 为什么要给容器开SSH场景、边界与思路1.1 哪些场景下真正需要先说结论不是所有容器都需要 SSH。如果你只是在本机开发用 docker exec 已经够快够方便没必要多开一个 22 端口给自己添乱。但下面几类场景SSH 登录的价值就很明显远程调试部署在服务器上的容器。你人不在服务器旁边但需要进容器看日志、执行命令、临时改配置。虽然可以先 SSH 到宿主机再 docker exec但多了一层中转命令也会变得冗长。批量管理多台机器上的容器。你可能有几台宿主机每台上跑了若干容器如果能直接 SSH 进容器配合自动化脚本批量执行命令会非常顺手。希望用标准终端工具接入。FileZilla、VS Code Remote、FinalShell、Xshell 这些工具都支持 SSH 协议。给容器开 SSH 后就能用这些工具像连普通服务器一样连容器文件上传下载、代码同步、远程编辑都顺带解决了。容器被当作轻量虚拟机使用。有些人喜欢把容器当成一个干净、可丢弃的 Ubuntu 环境在里面编译代码、跑脚本、装各种依赖。此时 SSH 登录能保留“标准 Linux 服务器”的操作习惯。如果你符合上面任意一条那这篇文章的配置方案就值得看完。1.2 docker exec 与 SSH 登录的取舍我经常被问docker exec 不是也能进容器吗为什么非要折腾 SSH两者确实都能进容器但使用逻辑完全不一样。docker exec 本质上依赖 Docker 引擎。宿主机上必须有 docker 命令而且你必须先有宿主机权限才能通过 exec 进入任何一个容器。它更像“进程注入”而不是“网络登录”。好处是无需密码、无需容器内启动服务坏处是离开宿主机这个入口就断了也没法被那些纯 SSH 客户端直接使用。SSH 登录则是标准的网络服务。容器内跑着 sshd监听某个端口客户端通过网络完成认证和登录。它把容器变成了一个“独立节点”不依赖 Docker CLI可以被任意 SSH 工具、自动化脚本、CI/CD 流程直接对接。代价是你要管理账号、密码或密钥、端口暴露还要保证 sshd 进程不会因为容器重启而消失。我个人的经验是本机快速调试用 docker exec正式环境、远程管理、自动化运维优先考虑 SSH。两者不是替代关系而是互补关系。1.3 什么时候不建议开SSH配置方法再顺手也有不适合开 SSH 的场景。如果你只是跑一个 Nginx、MySQL、Redis 这类单进程应用容器完全没必要往里面装 sshd。一来违背了容器“一个容器一个主进程”的主流实践二来多开一个网络服务就多一个安全暴露面。还有一点如果容器是临时创建的比如 CI 里跑完就删那配置 SSH 纯属浪费时间。另外如果你对容器安全没有概念我建议先别急着把端口暴露到公网。容器里的 root 账号、弱密码、不限制来源 IP任何一个环节出问题都可能把内部网络暴露给扫描器。这也是我为什么在后面单独讲安全底线。2. 准备工作镜像选择与容器启动2.1 选哪个Ubuntu镜像更合适标题直接写的是 Ubuntu那我们就用官方 ubuntu 镜像来演示。官方镜像有多个版本标签常用的有 20.04、22.04、24.04。我的建议是能用 LTS 就用 LTS因为维护周期长软件源稳定遇到问题容易搜到答案。演示我用 22.04读者自己换成 20.04 或 24.04 也没问题配置过程几乎一致。拉取镜像很简单docker pull ubuntu:22.04如果网络慢可以配置 Docker 镜像加速器这里不展开。拉完之后用docker images确认一下镜像已经在本地。还有一个容易忽略的点ubuntu 官方镜像默认非常精简连 vim、curl 都没有只有最基本的 coreutils。所以在容器里安装软件之前我习惯先把基础工具装上比如 vim、net-tools、iputils-ping省得配置到一半才发现连编辑器都没有只能靠 echo 拼文件。2.2 启动容器端口映射别漏启动容器这里非常关键因为端口映射决定了你后面能不能从宿主机访问容器内的 SSH 服务。命令如下docker run -itd --name ubuntu-ssh -p 2222:22 ubuntu:22.04解释一下参数--name ubuntu-ssh给容器起个名字方便后续用名字操作。-itd保持容器在后台运行同时保留终端交互能力。-p 2222:22把宿主机的 2222 端口映射到容器的 22 端口。宿主机的 2222 是外面访问的入口容器的 22 是 sshd 要监听的端口。ubuntu:22.04 是镜像名。为什么不直接用-p 22:22因为宿主机自己的 22 端口通常已经跑着 sshd 了直接映射会冲突。即使宿主机没开 SSH在演示环境里用 2222 也更能让你理解“端口映射”这个概念后面排查问题时思路更清晰。启动后进入容器docker exec -it ubuntu-ssh bash此时你会看到类似root容器ID:/#的提示符说明你已经进入了容器内部的 shell。2.3 容器与“小虚拟机”的思维转变给容器配置 SSH有一个思维上的转变要做不要把容器当成一个只跑业务的进程黑洞而要把它当成一个最小化的独立 Ubuntu 系统。虽然容器和虚拟机在底层隔离机制上不同但使用体验上接近——你有自己的文件系统、进程树、网络栈可以装软件、创建用户、跑服务。容器里的进程是宿主机内核上的进程只是通过命名空间实现隔离。这就是为什么容器这么轻量秒级启动但代价是你不能在容器里随便改内核参数也不建议在里面跑 systemd 做完整的服务管理。明白这一点你就知道为什么后面我们启动 sshd 用的是/usr/sbin/sshd命令而不是systemctl start ssh。3. 容器内安装并配置SSH服务3.1 安装openssh-server进入容器之后第一件事是更新软件源索引apt update然后安装 ssh 服务端apt install -y openssh-serverUbuntu 官方源里 openssh-server 的包名就是这个安装完成后会生成/etc/ssh/sshd_config、/usr/sbin/sshd等文件。安装过程中如果提示设置时区之类的交互选项直接默认回车即可。装完之后顺手装点诊断工具方便后面排查apt install -y vim net-tools iputils-ping3.2 修改sshd_config登录权限的命根子SSH 服务的行为基本都由/etc/ssh/sshd_config控制。这个文件很长默认很多项是注释状态表示使用默认值。我们只需要关心几个核心参数。先看当前登录相关配置grep -E PermitRootLogin|PasswordAuthentication|PubkeyAuthentication /etc/ssh/sshd_configUbuntu 官方镜像默认的配置大约是PermitRootLogin prohibit-password PasswordAuthentication yes PubkeyAuthentication yes这几个参数的含义PermitRootLogin prohibit-password允许 root 登录但只能通过密钥认证不能密码登录。如果你用的是非 root 用户或者你打算用密钥登录 root这个默认值其实还不错。PasswordAuthentication yes允许密码认证。对快速测试来说方便但生产环境建议改 no只留密钥。PubkeyAuthentication yes允许公钥认证这是安全登录的基石必须保证是 yes。如果你希望直接用 root 密码登录比如测试环境下图省事可以这样改sed -i s/#PermitRootLogin prohibit-password/PermitRootLogin yes/ /etc/ssh/sshd_config echo PermitRootLogin yes /etc/ssh/sshd_config.d/custom.conf这里我强烈建议不要直接改 sshd_config 里的原行而是在/etc/ssh/sshd_config.d/下新建一个自定义配置文件。因为 Ubuntu 的 sshd_config 末尾有一行Include /etc/ssh/sshd_config.d/*.conf凡是放在这个目录下的 conf 文件都会被加载而且优先级更高以后重置配置也更清晰。我实际用的自定义配置一般是cat /etc/ssh/sshd_config.d/custom.conf EOF PermitRootLogin yes PasswordAuthentication yes PubkeyAuthentication yes EOF如果你对安全要求高还可以加一个AllowUsers root来限制只允许指定账号登录。3.3 创建用户或设置root密码如果决定用密码登录那必须设置一个密码。root 默认没有密码直接设置passwd root按提示输入两遍新密码即可。这里要提醒一句不要在测试容器里用123456这种密码哪怕只是本地测试坏习惯养成后很容易带进生产环境。如果你想创建普通用户来登录可以这样useradd -m -s /bin/bash dev passwd dev-m表示自动创建家目录-s /bin/bash是登录 shell。Ubuntu 镜像里默认没有/home/dev不创建的话登录进去会出现“没有家目录”的尴尬。3.4 启动sshd并验证启动 sshd 之前必须先创建特权目录分离目录否则启动会报错mkdir -p /run/sshd然后启动/usr/sbin/sshd没有任何输出就是好消息。验证一下进程和端口ps -ef | grep sshd ss -tlnp | grep 22如果看到 sshd 在监听 0.0.0.0:22那就说明服务已经起来了。到这里容器内的配置就完成了一半。注意容器内通常没有 systemd所以不能直接systemctl start ssh。如果用了service ssh start它本质是调用 init 脚本在容器里也不一定可靠最稳的方式永远是用/usr/sbin/sshd自己把服务拉起来。4. 让SSH登录容器后不会“一重启就失效”4.1 容器重启后SSH为什么连不上很多人在容器里手动启动 sshd 后测试能连上然后开心地重启容器结果发现连接被拒绝。原因很简单容器重启后原来的进程全部被回收而 sshd 并没有任何机制随容器启动自动拉起。在宿主机上systemctl enable ssh会把 sshd 注册为开机自启服务但容器里没有 systemd也没有“开机”这个概念容器的生命周期由 Docker 控制。所以我们必须手动告诉容器启动时执行 sshd。这就是容器编排里常说的“主进程要自己拉起”。只要理解了这一点后面的自启动配置就顺理成章了。4.2 用CMD/ENTRYPOINT固定启动逻辑最简单的自启动方式是在docker run时覆盖容器的启动命令。比如docker run -itd --name ubuntu-ssh -p 2222:22 ubuntu:22.04 /usr/sbin/sshd -D-D参数表示让 sshd 以前台模式运行这样 sshd 就会成为容器的 PID 1 主进程。容器启动时执行它容器停止时它也退出生命周期完全绑定。但这种方式有个问题如果 sshd 意外退出容器也就退出了没有守护进程帮你把它拉起来。更稳妥的做法是写一个启动脚本在里面做初始化操作比如生成 host key、创建 /run/sshd 目录然后再启动 sshd。启动脚本/root/start.sh可以写成#!/bin/bash mkdir -p /run/sshd # 生成host key如果之前不存在 /usr/sbin/sshd-keygen 2/dev/null || true exec /usr/sbin/sshd -D注意exec很重要它能让 sshd 取代脚本进程成为 PID 1收到 SIGTERM 时能正确退出而不是留下僵硬的退出手续。然后重新创建容器时docker run -itd --name ubuntu-ssh -p 2222:22 ubuntu:22.04 bash /root/start.sh4.3 Dockerfile固化配置如果希望容器可以被反复重建而不用每次手动配置那就必须把整个过程写进 Dockerfile。这是我最推荐的做法因为可重复、可审计、可版本控制。看一个简单但完整的例子FROM ubuntu:22.04 RUN apt update apt install -y openssh-server vim net-tools \ mkdir -p /run/sshd \ echo root:change-me | chpasswd # 自定义 sshd 配置 RUN bash -c echo PermitRootLogin yes /etc/ssh/sshd_config.d/custom.conf RUN bash -c echo PasswordAuthentication yes /etc/ssh/sshd_config.d/custom.conf RUN bash -c echo PubkeyAuthentication yes /etc/ssh/sshd_config.d/custom.conf EXPOSE 22 CMD [/usr/sbin/sshd, -D]构建并启动docker build -t ubuntu-ssh . docker run -itd --name ubuntu-ssh -p 2222:22 ubuntu-ssh这样每次从镜像创建容器sshd 都是自动运行的。整个配置过程从手工操作变成了基础设施代码思路完全不一样了。提示密码最好不要写死在 Dockerfile 里可以用构建参数ARG或启动时通过环境变量传入。更理想的是直接用密钥登录彻底摆脱密码管理问题下面章节专门讲。5. 密钥登录实战免密且更安全5.1 生成密钥对密码登录用起来方便但安全性和体验都弱于密钥登录。密钥认证的核心原理是客户端持私钥服务器只存对应公钥。登录时服务器发送随机挑战客户端用私钥签名服务器用公钥验证。私钥不出客户端所以即使服务器被攻破也不会泄露你的私钥。在宿主机上生成密钥对如果你还没有ssh-keygen -t ed25519 -C docker-ubuntu-ssh直接用默认路径~/.ssh/id_ed25519即可。生成的私钥是id_ed25519公钥是id_ed25519.pub。建议用 ed25519 算法比 RSA 短、快现代 OpenSSH 都支持。老系统如果兼容性有疑虑可以用-t rsa -b 4096。5.2 把公钥放进容器把公钥放到容器里的方式有几种。如果容器里已经配置了允许密码登录可以用ssh-copy-idssh-copy-id -p 2222 rootlocalhost它会提示输入密码然后把公钥追加到容器内 root 用户的 authorized_keys 里。如果不方便用 ssh-copy-id手动操作也行。先在宿主机上查看公钥内容cat ~/.ssh/id_ed25519.pub然后在容器内手动创建授权文件mkdir -p /root/.ssh echo 你的公钥内容 /root/.ssh/authorized_keys5.3 权限问题与验证密钥认证对文件权限极其敏感这一点是新手踩坑重灾区。如果权限不对sshd 会直接拒绝使用你的密钥。需要满足chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys为什么这么严格因为 sshd 担心你的私钥或授权文件可以被其他用户读写从而被恶意替换。权限检查是它防篡改的第一道防线。然后验证免密登录ssh -p 2222 rootlocalhost如果一切正常你应该直接拿到容器 shell不需要输入密码。如果失败排查思路看后面第 7 节。验证成功后强烈建议把 sshd 配置里的PasswordAuthentication改成no只保留密钥认证。这样外部攻击者即使知道密码也登不进来安全等级完全不同。echo PasswordAuthentication no /etc/ssh/sshd_config.d/custom.conf改完记得重启 sshd或者说重启容器。6. 从宿主机和外部网络访问容器的完整链路6.1 本地访问与端口映射容器内 SSH 监听 22宿主机上通过 2222 端口映射访问。链路是SSH客户端 - 宿主机2222端口 - Docker NAT - 容器22端口在宿主机上测试ssh -p 2222 rootlocalhost能连上就说明整条链路没问题。这里有个经验如果你多台宿主机上都跑着这种容器建议给不同容器分配不同宿主机端口比如 2201、2202、2203避免冲突。还可以在/etc/hosts里加几条别名方便记忆。6.2 网络模式与端口冲突Docker 默认的 bridge 网络模式下端口映射靠 iptables 转发实现。如果你改了 Docker 的 iptables 规则或者宿主机防火墙拦了对应端口就会出现“容器内正常宿主机端口不通”的情况。实际排查时先确认容器内监听没问题再确认宿主机端口在听ss -tlnp | grep 2222如果宿主机有 UFW 防火墙记得放行端口sudo ufw allow 2222/tcp如果你用的是 host 网络模式启动容器--network host那容器直接共享宿主机网络栈sshd 就直接监听宿主机的 22 端口了。这种方式少了一层 NAT性能略好但端口不能随便自定义冲突风险也高一般不建议只是为了 SSH 登录就这么干。6.3 开放公网访问的安全底线远程访问容器如果跨越公网有几个底线必须守住禁止密码登录只用密钥认证杜绝弱口令爆破。不要暴露 root 登录或者给 root 单独设一个非常规端口。不要直接映射 22 到公网用非常规高位端口比如 32222。如果可能在防火墙层面限制来源 IP只允许你的办公网段访问。定期更新宿主机和容器内的 OpenSSH 版本避免已知漏洞。这套底线我踩过不少坑才总结出来。曾经图省事把某台测试机的容器 22 端口直接暴露到公网第二天就收到一堆来自国外 IP 的爆破日志幸好当时只开了密钥认证密码爆破根本没机会。从那以后我凡是涉及公网访问都一律先关密码认证再考虑别的。7. 常见问题与排查技巧实录7.1 高频问题速查表我把日常操作中最容易遇到的现象、原因和解决办法整理成一张表方便你直接对号入座。现象常见原因解决办法sshd启动报 Missing privilege separation directory: /run/sshd/run/sshd 目录不存在mkdir -p /run/sshd客户端连接被拒绝容器没做端口映射或 sshd 没启动检查 docker ps 和容器内 ss -tlnpPermission denied (publickey,password)密码错误、PermitRootLogin no、或账户被限制改正配置、检查 AllowUsers密钥登录失败但密码能登录authorized_keys 权限错误或公钥没放对chmod 700 ~/.ssh chmod 600 authorized_keys容器重启后连不上sshd 没有随容器自动启动用 CMD/ENTRYPOINT 方式启动 sshd宿主机防火墙挡住端口宿主机 UFW 或安全组未放行放行对应端口如 ufw allow 2222/tcpssh端口被占用宿主机 2222 已经有服务在听docker rm 旧容器换一个端口客户端报 Host key verification failed容器重建后 host key 变了清理客户端 known_hosts 中对应用户行7.2 三个我实际踩过的坑第一个坑是直接在容器里用service ssh start。服务确实能起来但容器重启后经常忘记再执行一次。后来我规定自己的容器一律用/usr/sbin/sshd -D作为主进程所有配置写进 Dockerfile从根上杜绝了“重启就失联”的问题。第二个坑是密钥权限。有次我在容器里手动把 authorized_keys 复制过来文件权限是 644结果怎么连都是 Permission denied。排查半天发现 sshd 日志里写着Authentication refused: bad ownership or modes。把权限改成 600 后立刻就好了。从那以后我凡是配置密钥第一反应就是检查权限。第三个坑是 Ubuntu 官方镜像的 root 密码默认是锁定状态。如果你没执行 passwd root就算 sshd_config 里写了 PermitRootLogin yes也没法用 root 登录。这个问题隐蔽在“客户端连接被拒绝”里面特别容易让人绕远路。所以配置完成后先用 docker exec 进容器执行一次 passwd root 或者创建普通用户再测试 SSH。再说一个真实场景有次我在远程服务器上配置一个容器配置完测试通过隔了一个月再连发现又不行了。排查后才知道是服务器重启过容器跟着 restart 策略拉起来了但 sshd 没自动运行。如果你也用docker run --restart always跑容器请一定确认容器的主进程是 sshd或者启动脚本里把 sshd 拉起来否则就会出现“容器还活着但 SSH 失效”的尴尬局面。写在最后一点实操体会给 Docker 容器配 SSH 登录这件事本质上是把容器的可运维性提升到和虚拟机一样的水平。我后来做批量环境时习惯把所有基础容器都做成 Dockerfile 模板装好 sshd、设好密钥再统一映射端口。这样无论是本地开发还是远程部署我都能用熟悉的 SSH 工具直接接入。如果你只是在单机环境测试手动执行一遍上面的命令理解每个步骤在做什么后面遇到问题就心里有底了。最后提醒一句无论环境多简单都别养成用弱密码的习惯配置完 SSH 之后优先把密码认证关掉换成密钥登录这个习惯能帮你省下无数潜在的安全麻烦。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑