用 Docker 搭建可 SSH 的隔离实验环境
以 Ubuntu 容器为例,记录 SSH 接入、镜像迁移与可复现构建的取舍
本文用 Docker 搭建一个接近传统服务器使用方式的隔离实验环境,并讨论如何迁移和复现它。
警告这是一套兼容 SSH 运维习惯的实验环境方案,不是通用的生产部署模板。普通应用容器更适合只运行一个主要服务,通过
docker exec、容器日志和编排平台管理;只有明确需要远程 shell 的隔离实验或兼容场景,才建议在容器内运行sshd。
你有没有遇到过这种情况:在一台云服务器上辛辛苦苦配好了环境、装好了各种依赖,结果某天需要换服务商或者加一台机器,发现所有事情得从头来一遍?
这正是我开始用 Docker 搭建“子服务器”式实验环境的原因。它适合临时隔离依赖、复现实验步骤,也能把环境迁到另一台装有 Docker 的机器上。长期运行的正式服务,仍应优先用 Dockerfile 和 Compose 描述,而不是把容器当作完整虚拟机维护。
为什么选择 Docker?
直接在云服务器上部署应用当然可以,但时间一长你会发现几个头疼的问题:环境难以复现、迁移成本高、不同应用之间互相干扰。Docker 恰好解决了这些痛点。
先来看一下两种部署方式的对比:
| 对比维度 | 直接部署 | Docker 部署 |
|---|---|---|
| 环境一致性 | 每台机器手动配置,容易出现“我这能跑” | Dockerfile 和镜像固定后更容易复现 |
| 迁移成本 | 需要重新安装依赖 | 镜像可迁移,数据卷和参数另行处理 |
| 应用隔离 | 共享系统环境,依赖可能冲突 | 容器独立运行,互不干扰 |
| 资源占用 | 直接使用系统资源,无额外开销 | 轻微的容器开销,远低于虚拟机 |
| 启动速度 | 取决于应用本身 | 秒级启动 |
| 回滚能力 | 依赖人工备份 | 应用镜像可回滚,数据兼容性仍需验证 |
简单来说,Docker 能把应用所需的文件和依赖封装进镜像;但数据卷、密钥和运行参数仍要单独管理,它并不是整台服务器的无损快照。
Docker 基础操作指南
接下来我们从零开始,一步一步把容器跑起来。
配置 Docker 权限
如果不想每次执行命令都加 sudo,可以把当前用户加入 docker 用户组:
sudo usermod -aG docker $USER
警告添加用户组后必须重新登录才能生效。还要注意,docker 用户组可以控制 Docker daemon,实际权限接近宿主机 root;多人服务器上不要为了省一个
sudo就随意授权,也可以考虑 Docker Rootless 模式。
Docker 容器管理
创建并运行容器
一行命令就能创建一个基于 Ubuntu 的容器,同时把 SSH 端口映射出来:
docker run --name my_server -p 20000:22 -itd ubuntu:24.04
参数说明:
| 参数 | 作用 |
|---|---|
--name my_server | 指定容器名称,方便后续管理 |
-p 20000:22 | 将容器的 22 端口映射到宿主机的 20000 端口 |
-itd | 交互式终端 + 后台运行 |
ubuntu:24.04 | 使用的基础镜像 |
端口映射只是把宿主机的 20000 端口转到容器 22 端口。Ubuntu 基础镜像默认没有安装和启动 SSH 服务,因此这条命令执行后还不能直接 SSH;完整配置请看后面的 Dockerfile 示例。
进入容器
容器跑起来之后,优先用 docker exec 打开一个独立 shell:
docker exec -it my_server bash
容器操作技巧
在 docker exec 打开的 shell 中输入 exit,只会结束这次 shell,不会停止容器。docker attach 是连接容器主进程,退出方式更容易影响容器状态,日常排查通常没必要用它。
一次性容器迁移方案
如果手里只有一个已经被人工修改过的容器,可以用 docker commit 做一次性迁移。它适合救急或实验留档,不适合作为日常发布流程:操作历史难以审查,也无法稳定复现。
重要
docker commit默认只保存容器的可写层,不包含挂载卷中的数据,也不会替你记录端口、密钥和其他运行参数。正式环境优先维护 Dockerfile / Compose 文件并把镜像推送到镜像仓库;数据卷要单独备份。
graph LR
A[运行中的容器] -->|docker commit| B[本地镜像]
B -->|docker save| C[.tar 文件]
C -->|scp 传输| D[目标服务器]
D -->|docker load| E[目标镜像]
E -->|docker run| F[新容器]
容器迁移步骤清单
- 用
docker commit将运行中的容器保存为镜像 - 用
docker save将镜像导出为.tar文件 - 单独备份挂载卷,并记录端口、环境变量等运行参数
- 设置文件权限,用 SCP 或
rsync传输到目标服务器 - 在目标服务器上用
docker load加载镜像 - 用
docker run从镜像创建新容器并验证服务正常
1.将容器保存为镜像
docker commit my_server my_custom_image:v1
2.将镜像导出为文件
docker save -o my_server_image.tar my_custom_image:v1
3.设置文件权限并传输
chmod +r my_server_image.tar
scp my_server_image.tar user@new-server:/path/to/destination
提示镜像文件可能很大(几百 MB 到几 GB)。如果服务器带宽有限,可以先
gzip压缩再传输:gzip my_server_image.tar,到目标机器后gunzip解压即可。
4.在新服务器上加载镜像
docker load -i my_server_image.tar
5.从镜像创建新容器
docker run --name my_server -p 20000:22 -itd my_custom_image:v1
走完这些步骤,容器可写层中的文件会进入新镜像。挂载卷、密钥和运行时参数仍需单独恢复,并在目标服务器上完成一次实际启动验证。
容器配置技巧
容器跑起来之后,还有几个实用的配置值得做。
加速软件安装
如果你的服务器在国内,apt 默认的源下载速度会很慢。换成清华大学开源软件镜像站可以大幅提升速度。
这一步建议在容器创建后第一时间做,后面装什么都会快很多。
用户管理
长期环境不建议一直用 root 操作。可以创建普通用户,并按实际需要分配权限:
adduser username
usermod -aG sudo username
自定义登录欢迎信息
如果你管理多个容器,自定义一下登录时的欢迎信息可以帮你快速区分当前在哪个环境里:
nano /etc/update-motd.d
Dockerfile 最佳实践
如果你经常需要创建类似的容器,手动配置就太低效了。写一个 Dockerfile 把初始化步骤固化下来,后续一行 docker build 就能产出一个开箱即用的镜像。
Dockerfile 示例
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
openssh-server ca-certificates curl \
&& useradd --create-home --shell /bin/bash admin \
&& mkdir -p /run/sshd /home/admin/.ssh \
&& chown -R admin:admin /home/admin/.ssh \
&& chmod 700 /home/admin/.ssh \
&& rm -rf /var/lib/apt/lists/*
COPY --chown=admin:admin authorized_keys /home/admin/.ssh/authorized_keys
RUN chmod 600 /home/admin/.ssh/authorized_keys
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D", "-e"]
把允许登录的 SSH 公钥写入与 Dockerfile 同目录的 authorized_keys,再构建镜像。示例没有在镜像层里写死密码;需要管理容器时,可以从宿主机执行 docker exec -u root,避免给 SSH 用户长期开放 root 权限。
几个关键点:
- 合并 RUN 指令:每个
RUN都会产生一层镜像,合并能显著减小镜像体积 - 清理缓存:
rm -rf /var/lib/apt/lists/*清理 apt 缓存,进一步缩小镜像 - 使用
--no-install-recommends:只装必要的包,不装推荐包 - 不把密码写进镜像:SSH 使用公钥认证,密钥和其他敏感信息不进入 Dockerfile
更多 Dockerfile 编写技巧可参考 Docker 官方最佳实践文档。
常见问题排查
注意以下是我踩过的一些坑,提前了解能省不少时间。
容器启动后立即退出
通常是因为容器没有前台进程。确保用了 -itd 参数,或者容器内有一个持续运行的服务(如 sshd)。用 docker logs my_server 查看退出原因。
SSH 连不上容器
检查这几项:容器内是否安装了 openssh-server、sshd 服务是否启动、端口映射是否正确、防火墙是否放行了映射端口。
docker load 报错 no space left on device
Docker 默认的存储目录在 /var/lib/docker,磁盘空间不够就会报这个错。用 docker system prune 清理无用的镜像和容器,或者通过 daemon.json 修改存储路径。
scp 传输速度很慢
除了前面提到的 gzip 压缩外,还可以用 rsync --progress 替代 scp,支持断点续传,传大文件更稳。
写在最后
把 Docker 当作隔离实验环境,确实能减少依赖冲突,也方便复现问题。但想把它长期维护好,关键仍然是用 Dockerfile 固化步骤、用数据卷保存数据,并把密钥和运行参数放在镜像之外。
如果只是临时迁移一个手工配置过的容器,commit + save/load 可以救急;如果要持续部署,就尽早切回可审查、可重复构建的流程。