← 文章

用 Docker 搭建可 SSH 的隔离实验环境

以 Ubuntu 容器为例,记录 SSH 接入、镜像迁移与可复现构建的取舍

文章目录6 节
  1. 为什么选择 Docker?
  2. Docker 基础操作指南
  3. 配置 Docker 权限
  4. Docker 容器管理
  5. 一次性容器迁移方案
  6. 容器配置技巧
  7. 加速软件安装
  8. 用户管理
  9. 自定义登录欢迎信息
  10. Dockerfile 最佳实践
  11. 常见问题排查
  12. 写在最后

文章目录

6 节
  1. 为什么选择 Docker?
  2. Docker 基础操作指南
  3. 配置 Docker 权限
  4. Docker 容器管理
  5. 一次性容器迁移方案
  6. 容器配置技巧
  7. 加速软件安装
  8. 用户管理
  9. 自定义登录欢迎信息
  10. Dockerfile 最佳实践
  11. 常见问题排查
  12. 写在最后

本文用 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 文件
  • 单独备份挂载卷,并记录端口、环境变量等运行参数
  • 设置文件权限,用 SCPrsync 传输到目标服务器
  • 在目标服务器上用 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 可以救急;如果要持续部署,就尽早切回可审查、可重复构建的流程。