Java 项目部署之 Docker工具快速入门: 镜像命令从 pull 到 load 的完整实操

概述

镜像命令一共就七个,但每个都有能让人卡半天的细节:tag 不写会拉到最新版、rmi 删不掉会报 conflict、save 出来的文件在离线机房才是唯一救命稻草。本文按"获取 → 查看 → 删除 → 导出导入 → 推送"的实际使用顺序拆一遍,配上真实报错和排查动作。

纲要

  • 镜像命名 :[repository]:[tag],tag 缺省即 latest
  • 七个核心命令 :pull / images / rmi / save / load / build / push
  • 两条获取路径 :从 Registry docker pull,从 Dockerfile docker build
  • 两条分发路径 :推到仓库 docker push,打成文件 docker save + U 盘 + docker load
  • 镜像流转全景:Mermaid 图表达四种流向
  • 离线迁移实操 :用资料目录里的 mysql.tar(377 MB)走一遍内网交付
  • 自查方法 :docker xx --help 比背命令管用
  • 实战坑 :镜像名写错、rmi 被容器占用、save 与 export 混用、空压缩包

镜像名到底怎么写

Docker 的镜像名固定两段:

text 复制代码
[repository]:[tag]
     │          │
     │          └── 版本,不写就是 latest
     └── 镜像仓库名,通常是软件名

mysql:5.7 和 mysql:5.6 是两个完全不同的镜像,ID、分层、体积都不一样。冒号不能省成横杠,写成 mysql-5.7 会被当成另一个仓库名去找,结果就是找不到。

tag 省略时 Docker 补 latest。这个默认值在生产上是隐患:

写法 实际解析 风险
docker pull nginx nginx:latest 今天拉的是 1.25,下个月可能是 1.27,环境不可复现
docker pull nginx:1.21.6 精确版本 任何时间拉到的都是同一个镜像
docker pull nginx:alpine alpine 变体 体积从 187 MB 降到 40 MB 左右,但 shell 是 sh 不是 bash

CI 里写死具体版本号,latest 只留给本地随手试。

还有一层:repository 前面可以带仓库地址,完整形态是 [registry host]/[项目或命名空间]/[repository]:[tag]。推私有仓库时必须写全,否则 Docker 默认往 DockerHub 推。

命令速查

命令 作用 示例
docker pull 从镜像仓库拉取镜像 docker pull nginx:1.21.6
docker images 列出本地已有镜像 docker images
docker rmi 删除本地镜像(remove image) docker rmi nginx:latest
docker save 把镜像导出成 tar 包文件 docker save -o nginx.tar nginx:latest
docker load 从 tar 包把镜像加载回本地 docker load -i nginx.tar
docker build 按 Dockerfile 构建镜像 docker build -t docker-demo:1.0 .
docker push 把镜像推送到镜像仓库 docker push registry.demo.com/team/demo:1.0
docker tag 给镜像起别名 / 加仓库前缀 docker tag nginx:latest registry.demo.com/base/nginx:1.21.6

rmi 是 remove image 的缩写,和 Linux 的 rm -i(交互式删除)没有任何关系。别把它记成 rm image。

镜像的四种流向

#mermaid-svg-iqHECqRFAEOp526A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iqHECqRFAEOp526A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iqHECqRFAEOp526A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iqHECqRFAEOp526A .error-icon{fill:#552222;}#mermaid-svg-iqHECqRFAEOp526A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iqHECqRFAEOp526A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iqHECqRFAEOp526A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iqHECqRFAEOp526A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iqHECqRFAEOp526A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iqHECqRFAEOp526A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iqHECqRFAEOp526A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iqHECqRFAEOp526A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iqHECqRFAEOp526A .marker.cross{stroke:#333333;}#mermaid-svg-iqHECqRFAEOp526A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iqHECqRFAEOp526A p{margin:0;}#mermaid-svg-iqHECqRFAEOp526A .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iqHECqRFAEOp526A .cluster-label text{fill:#333;}#mermaid-svg-iqHECqRFAEOp526A .cluster-label span{color:#333;}#mermaid-svg-iqHECqRFAEOp526A .cluster-label span p{background-color:transparent;}#mermaid-svg-iqHECqRFAEOp526A .label text,#mermaid-svg-iqHECqRFAEOp526A span{fill:#333;color:#333;}#mermaid-svg-iqHECqRFAEOp526A .node rect,#mermaid-svg-iqHECqRFAEOp526A .node circle,#mermaid-svg-iqHECqRFAEOp526A .node ellipse,#mermaid-svg-iqHECqRFAEOp526A .node polygon,#mermaid-svg-iqHECqRFAEOp526A .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iqHECqRFAEOp526A .rough-node .label text,#mermaid-svg-iqHECqRFAEOp526A .node .label text,#mermaid-svg-iqHECqRFAEOp526A .image-shape .label,#mermaid-svg-iqHECqRFAEOp526A .icon-shape .label{text-anchor:middle;}#mermaid-svg-iqHECqRFAEOp526A .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iqHECqRFAEOp526A .rough-node .label,#mermaid-svg-iqHECqRFAEOp526A .node .label,#mermaid-svg-iqHECqRFAEOp526A .image-shape .label,#mermaid-svg-iqHECqRFAEOp526A .icon-shape .label{text-align:center;}#mermaid-svg-iqHECqRFAEOp526A .node.clickable{cursor:pointer;}#mermaid-svg-iqHECqRFAEOp526A .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iqHECqRFAEOp526A .arrowheadPath{fill:#333333;}#mermaid-svg-iqHECqRFAEOp526A .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iqHECqRFAEOp526A .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iqHECqRFAEOp526A .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iqHECqRFAEOp526A .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iqHECqRFAEOp526A .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iqHECqRFAEOp526A .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iqHECqRFAEOp526A .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iqHECqRFAEOp526A .cluster text{fill:#333;}#mermaid-svg-iqHECqRFAEOp526A .cluster span{color:#333;}#mermaid-svg-iqHECqRFAEOp526A div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iqHECqRFAEOp526A .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iqHECqRFAEOp526A rect.text{fill:none;stroke-width:0;}#mermaid-svg-iqHECqRFAEOp526A .icon-shape,#mermaid-svg-iqHECqRFAEOp526A .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iqHECqRFAEOp526A .icon-shape p,#mermaid-svg-iqHECqRFAEOp526A .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iqHECqRFAEOp526A .icon-shape .label rect,#mermaid-svg-iqHECqRFAEOp526A .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iqHECqRFAEOp526A .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iqHECqRFAEOp526A .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iqHECqRFAEOp526A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地 Docker
远端
docker pull
docker build
docker save -o
docker load -i
docker push
docker rmi
DockerHub / 私有 Registry
本地镜像仓库

nginx:latest
磁盘文件

nginx.tar
Dockerfile
删除

这张图就是镜像操作的全部。pull/push 走网络,save/load 走文件,build 走源码,rmi 是出清。

为什么要分 save/load 和 push/pull 两套?因为它们解决的是不同场景:push 要求两端都能连仓库,适合 CI/CD;save 不需要网络,适合内网隔离环境、涉密机房、或者客户现场只给 U 盘的情况。

获取镜像:pull 与 build

docker pull

从仓库拉:

bash 复制代码
# 拉最新版
docker pull nginx

# 拉指定版本,推荐
docker pull nginx:1.21.6

# 从私有仓库拉
docker pull registry.demo.com/base/nginx:1.21.6

拉取过程是一层一层下的,输出里那些 Pull complete 就是分层:

text 复制代码
1.21.6: Pulling from library/nginx
a2abf6c4d29d: Pull complete
a9edb18cadd1: Pull complete
589b7251471a: Pull complete
Digest: sha256:...
Status: Downloaded newer image for nginx:1.21.6

国内拉 DockerHub 常常卡在几十 KB/s。配镜像加速:

bash 复制代码
# /etc/docker/daemon.json
{
  "registry-mirrors": ["https://<你的加速地址>"]
}
bash 复制代码
systemctl daemon-reload
systemctl restart docker

docker build

本地构建走 Dockerfile。资料目录里那个 Dockerfile 是完整的 JDK + jar 打包案例:

text 复制代码
day03-Docker/资料/
├── Dockerfile              # 构建脚本
├── docker-demo.jar         # 25 MB,待部署的 Java 产物
├── jdk8.tar.gz             # 185 MB,JDK 安装包
├── mysql.tar               # 377 MB,MySQL 镜像导出包
├── redis.tar.gz            # 22 B,空压缩包,不可用
├── nginx.conf              # Nginx 配置,配合容器挂载
└── hmy.cnf                 # MySQL 配置,配合容器挂载

Dockerfile 内容:

dockerfile 复制代码
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK 的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝 jdk 和 java 项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装 JDK
RUN cd $JAVA_DIR \
 && tar -xf ./jdk8.tar.gz \
 && mv ./jdk1.8.0_144 ./java8

# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin

# 暴露端口
EXPOSE 8090
# 入口,java 项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

构建:

bash 复制代码
# -t 指定镜像名和 tag,末尾的 . 是构建上下文路径,不是 Dockerfile 路径
docker build -t docker-demo:1.0 .

末尾那个 . 漏掉会报:

text 复制代码
"docker build" requires exactly 1 argument.

它的含义是"把当前目录作为上下文传给 Docker 守护进程",所以 COPY ./jdk8.tar.gz 才能在上下文里找到文件。如果 Dockerfile 不在当前目录,加 -f:

bash 复制代码
docker build -f ./docker/Dockerfile -t docker-demo:1.0 .

查看镜像:images

bash 复制代码
docker images

输出:

text 复制代码
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    605c77e624dd   4 days ago     141MB
mysql        5.7       c20987f18b13   3 months ago   448MB

每行几个字段的含义:

字段 说明
REPOSITORY 镜像仓库名,冒号前的部分
TAG 版本,没指定就是 latest
IMAGE ID 镜像唯一 ID,SHA256 的前 12 位,删除时可用它指代
CREATED 镜像构建时间,不是你拉取的时间
SIZE 解压后占用的磁盘大小,不是 tar 包体积

SIZE 显示 141 MB 的 nginx,实际 save 出来的 tar 也差不多这个量级,但如果是多层共享的镜像,docker images 里各镜像 SIZE 加起来会远大于真实占用------底层只读层是被共享的。

常用变体:

bash 复制代码
docker images                     # 全部镜像
docker images nginx               # 只看 nginx 的各个 tag
docker images -q                  # 只输出 IMAGE ID,常配合批量删除
docker images -a                  # 含中间层镜像
docker images --digests           # 显示摘要,校验镜像是否被篡改时用
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"   # 自定义列

批量清理的经典组合:

bash 复制代码
# 删除所有 dangling(无 tag 的虚悬镜像),常见于反复 build 同名的产物
docker rmi $(docker images -q -f "dangling=true")

删除镜像:rmi

bash 复制代码
# 按 名称:tag 删
docker rmi nginx:latest

# 按 IMAGE ID 删,ID 写前几位能唯一识别即可
docker rmi 605c

# 强制删
docker rmi -f nginx:latest

输出:

text 复制代码
Untagged: nginx:latest
Deleted: sha256:605c77e624dd...

注意这里有两步:Untagged 先摘掉标签,Deleted 才真正删层。如果同一个镜像 ID 挂着多个 tag(比如你 tag 过一次),rmi 只会 Untagged,镜像本体还在。这是很多人"明明删了怎么还在"的原因。

删不掉:镜像被容器占用

这是 rmi 最高频的报错。按容器状态分两种:

text 复制代码
# 有已停止的容器引用它
Error response from daemon: conflict: unable to delete 605c77e624dd (must be forced) - image is being used by stopped container 8e2a1b3c4d5e

# 有运行中的容器引用它
Error response from daemon: conflict: unable to remove repository reference "nginx:latest" (must force) - container 8e2a1b3c4d5e is using its referenced image

排查顺序:

bash 复制代码
# 找出哪些容器在用这个镜像,含已停止的
docker ps -a --filter ancestor=nginx:latest

# 先停
docker stop 8e2a1b3c4d5e

# 再删容器
docker rm 8e2a1b3c4d5e

# 最后删镜像
docker rmi nginx:latest

docker rm 删的是容器,docker rmi 删的是镜像,差一个字母,别混。

rmi -f 能强删,但代价是被删镜像对应的容器会变成"孤儿"------容器还在,镜像层没了,重启这个容器必然失败。除非你就是要清磁盘,否则老老实实先删容器。

导出与导入:save 与 load

这一对是离线环境的唯一解法。

save:镜像变文件

先看帮助:

bash 复制代码
docker save --help
text 复制代码
Usage:  docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Options:
  -o, --output string   Write to a file, instead of STDOUT

-o 是 output,指定输出文件。命令格式:

bash 复制代码
docker save -o [保存的目标文件名称] [镜像名称]

实操:

bash 复制代码
# 导出 nginx:latest 到当前目录的 nginx.tar
docker save -o nginx.tar nginx:latest

# 一次导出多个镜像到同一个包
docker save -o base.tar nginx:1.21.6 mysql:5.7 redis:6.2

# 不写 -o 则输出到标准输出,配合重定向
docker save nginx:latest > nginx.tar

ls -lh 看一下,文件就出来了:

text 复制代码
-rw-------  1 root root 141M Sep 29 15:20 nginx.tar

load:文件变镜像

bash 复制代码
docker load --help
text 复制代码
Usage:  docker load [OPTIONS]

Load an image from a tar archive or STDIN

Options:
  -i, --input string   Read from tar archive file, instead of STDIN
  -q, --quiet          Suppress the load output

-i 是 input,读哪个文件;-q 是静默,加了就不打印加载日志。

完整验证流程------先删后加载,确认这个 tar 真的可用:

bash 复制代码
# 1. 删掉本地镜像
docker rmi nginx:latest

# 2. 确认已经没了
docker images

# 3. 从文件加载回来
docker load -i nginx.tar

# 4. 再看,镜像回来了,tag 也保留着
docker images

load 的输出:

text 复制代码
Loaded image: nginx:latest

不带 -q 时会看到逐层加载的日志,内网排查的时候别加 -q,看不到进度会以为卡死了。

save 与 export 别混

对比项 docker save / docker load docker export / docker import
操作对象 镜像 容器(的运行状态快照)
保留分层历史 保留 丢弃,压成一层
保留 CMD / ENTRYPOINT 保留 丢失,要重新指定
保留环境变量、卷声明 保留 丢失
典型用途 镜像迁移、离线交付 把改过的容器文件系统存成底包

结论:迁移镜像一律用 save/load 。export 出来的镜像再 run 会报 no command specified,因为它不知道该跑什么。

离线迁移实操:把 MySQL 搬到内网

资料目录里那个 mysql.tar 377 MB,就是给这种场景准备的。假设客户机房不连外网,交付流程是这样:
内网服务器 移动介质 外网开发机 内网服务器 移动介质 外网开发机 #mermaid-svg-EgdU2LQknCDFzkF0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EgdU2LQknCDFzkF0 .error-icon{fill:#552222;}#mermaid-svg-EgdU2LQknCDFzkF0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EgdU2LQknCDFzkF0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EgdU2LQknCDFzkF0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EgdU2LQknCDFzkF0 .marker.cross{stroke:#333333;}#mermaid-svg-EgdU2LQknCDFzkF0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EgdU2LQknCDFzkF0 p{margin:0;}#mermaid-svg-EgdU2LQknCDFzkF0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EgdU2LQknCDFzkF0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EgdU2LQknCDFzkF0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-EgdU2LQknCDFzkF0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-EgdU2LQknCDFzkF0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-EgdU2LQknCDFzkF0 .sequenceNumber{fill:white;}#mermaid-svg-EgdU2LQknCDFzkF0 #sequencenumber{fill:#333;}#mermaid-svg-EgdU2LQknCDFzkF0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-EgdU2LQknCDFzkF0 .messageText{fill:#333;stroke:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EgdU2LQknCDFzkF0 .labelText,#mermaid-svg-EgdU2LQknCDFzkF0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .loopText,#mermaid-svg-EgdU2LQknCDFzkF0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EgdU2LQknCDFzkF0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-EgdU2LQknCDFzkF0 .noteText,#mermaid-svg-EgdU2LQknCDFzkF0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-EgdU2LQknCDFzkF0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EgdU2LQknCDFzkF0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EgdU2LQknCDFzkF0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EgdU2LQknCDFzkF0 .actorPopupMenu{position:absolute;}#mermaid-svg-EgdU2LQknCDFzkF0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-EgdU2LQknCDFzkF0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EgdU2LQknCDFzkF0 .actor-man circle,#mermaid-svg-EgdU2LQknCDFzkF0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-EgdU2LQknCDFzkF0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker pull mysql:5.7 docker save -o mysql.tar mysql:5.7 拷贝 mysql.tar(377 MB) 上传至 /opt/images/ docker load -i /opt/images/mysql.tar docker images 确认 tag 为 5.7 docker run 启动容器

外网侧:

bash 复制代码
# 拉
docker pull mysql:5.7

# 导出,注意带上 tag
docker save -o mysql.tar mysql:5.7

# 校验一下文件确实写完整了,377 MB 量级的文件传输中断很常见
ls -lh mysql.tar
sha256sum mysql.tar > mysql.tar.sha256

内网侧:

bash 复制代码
# 校验完整性
sha256sum -c mysql.tar.sha256

# 加载
docker load -i mysql.tar

# 确认
docker images mysql

启动并挂上资料目录里的配置:

bash 复制代码
docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123 \
  -v /opt/mysql/data:/var/lib/mysql \
  -v /opt/mysql/conf:/etc/mysql/conf.d \
  mysql:5.7

hmy.cnf 放 /opt/mysql/conf 下:

ini 复制代码
[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000

两个真实的文件坑

坑一:后缀不代表格式。 资料目录里的 redis.tar.gz 只有 22 字节,file 一下会发现它是个空的 ZIP:

bash 复制代码
$ file redis.tar.gz
redis.tar.gz: Zip archive data (empty)

load 它只会得到一句干巴巴的报错:

text 复制代码
open /var/lib/docker/tmp/docker-import-xxx/.dockerenv: not a directory

或者更常见的:

text 复制代码
Error processing tar file(exit status 1): unexpected EOF

所以拿到包先 ls -lh 看体积,再 file 确认类型,别直接 load。

坑二:.gz 后缀的镜像包未必是 gzip。 docker save 产出的是未压缩的 tar,想省空间得自己压:

bash 复制代码
# 导出并压缩,377 MB 能压到 100 MB 出头
docker save mysql:5.7 | gzip > mysql.tar.gz

# 加载时解压
gunzip -c mysql.tar.gz | docker load

而资料目录里的 jdk8.tar.gz 是 gzip 压缩的 JDK 安装包(给 Dockerfile 里 tar -xf 用的),不是 Docker 镜像包。两者名字一样,用途完全不同------判断依据是有没有 manifest.json 这个文件:

bash 复制代码
# 真镜像包里有 manifest.json 和一串层目录
tar -tf mysql.tar | head -5
# mysql.tar 会输出:
# 0e5b0e0f0e0f.../json
# 1a2b3c4d5e6f.../layer.tar
# manifest.json
# repositories

推送镜像:tag 与 push

推之前必须先把镜像名改成带仓库地址的形态,Docker 靠镜像名判断推去哪:

bash 复制代码
# 语法:docker tag 源镜像 目标镜像
docker tag docker-demo:1.0 registry.demo.com/team/docker-demo:1.0

# 一个镜像可以有多个名字,images 里会重复显示,IMAGE ID 相同
docker images

登录并推送:

bash 复制代码
docker login registry.demo.com
docker push registry.demo.com/team/docker-demo:1.0

私有仓库走 http 时 Docker 默认拒绝,要在 /etc/docker/daemon.json 加白名单:

json 复制代码
{
  "insecure-registries": ["registry.demo.com:5000"]
}
bash 复制代码
systemctl daemon-reload
systemctl restart docker

忘了这步会报:

text 复制代码
http: server gave HTTP response to HTTPS client

push 之前还有一个常见疏漏:没 login。报错长这样:

text 复制代码
denied: requested access to the resource is denied

忘了命令怎么办:--help

命令记不住是常态,正确的做法是查帮助。这个能力比背命令值钱。

bash 复制代码
# 看 Docker 全部命令
docker --help

# 看某个命令的详细用法
docker images --help
docker save --help
docker load --help

docker images --help 的输出:

text 复制代码
Usage:  docker images [OPTIONS] [REPOSITORY[:TAG]]

List images

Options:
  -a, --all             Show all images (default hides intermediate images)
      --digests         Show digests
  -f, --filter filter   Filter output based on conditions provided
      --format string   Pretty-print images using a Go template
      --no-trunc        Don't truncate output
  -q, --quiet           Only show image IDs

Usage 那行就是语法模板,方括号是可选项,大写词是占位符。看懂这一行,命令基本就能写对。

实战坑汇总

现象 报错 原因 处理
镜像名写错 pull access denied for mysql-5.7, repository does not exist 冒号写成横杠,被当成另一个仓库名 改回 mysql:5.7
tag 写成版本前缀 manifest for nginx:1.21 not found tag 必须完整匹配,没有模糊匹配 写全 1.21.6,或去 DockerHub 查真实 tag
rmi 删不掉 image is being used by stopped container xxx 已停止的容器仍引用该镜像 docker rm xxx 后再 rmi
rmi 删不掉 container xxx is using its referenced image 运行中的容器在引用 docker stop xxx && docker rm xxx
rmi 只 Untagged 输出只有 Untagged: 没有 Deleted: 同一镜像 ID 挂着多个 tag 把所有 tag 都 rmi 掉,或用 IMAGE ID 删
磁盘没释放 rmi 成功但 df -h 没变小 有 dangling 层或容器可写层残留 docker system df 看占用,docker system prune -a 清理
load 失败 unexpected EOF tar 包传输不完整或是空文件 ls -lh 核对体积,sha256sum 校验
build 报参数错 requires exactly 1 argument 漏了末尾的构建上下文 . 补上 .
push 被拒 requested access to the resource is denied 未登录或命名空间无权限 docker login 后重试
push 协议错 server gave HTTP response to HTTPS client 私有仓库是 http daemon.json 加 insecure-registries

完整演练:从零走一遍

把上面所有命令串起来,一条龙:

bash 复制代码
# 1. 拉镜像
docker pull redis:6.2

# 2. 查看
docker images

# 3. 导出
docker save -o redis.tar redis:6.2
ls -lh redis.tar

# 4. 删除
docker rmi redis:6.2

# 5. 确认已删除
docker images

# 6. 加载回来
docker load -i redis.tar

# 7. 再确认
docker images

命令速览

命令 作用
docker pull nginx:1.21.6 拉取指定版本镜像
docker images 列出本地镜像
docker images -q 只输出镜像 ID
docker images -f "dangling=true" 筛出虚悬镜像
docker rmi nginx:latest 删除镜像
docker rmi -f 605c 按 ID 强制删除
docker save -o nginx.tar nginx:latest 导出镜像为 tar
`docker save nginx:latest gzip > nginx.tar.gz`
docker load -i nginx.tar 从 tar 导入镜像
docker load -q -i nginx.tar 静默导入
docker build -t demo:1.0 . 按 Dockerfile 构建
docker tag demo:1.0 reg.com/team/demo:1.0 打仓库前缀标签
docker push reg.com/team/demo:1.0 推送到仓库
docker save --help 查命令用法
docker system df 看 Docker 真实磁盘占用

官方文档

总结

  • 镜像名 [repository]:[tag],tag 缺省是 latest。CI 里写死版本号,latest 只留给本地试。
  • 获取有两条路:pull 从仓库拿,build 从 Dockerfile 造。分发也有两条路:push 走网络,save/load 走文件------内网隔离环境只有后者可用。
  • rmi 删不掉几乎都是容器在引用。先 docker ps -a --filter ancestor=<镜像> 找出来,rm 容器再 rmi 镜像,-f 强删会留下孤儿容器。
  • save 保留分层和 ENTRYPOINT,export 只存文件系统快照。迁移镜像只用前者。
  • save 出来是未压缩 tar,.gz 后缀不代表 gzip;拿到包先 ls -lh + file 验一遍再 load。
  • 命令不用背,docker xx --help 的 Usage 行就是语法模板。
相关推荐
贾宝玉的玉宝贾1 小时前
FreeSWITCH 简单图形化界面 70 - 监听/密语的lua脚本
开发语言·junit·lua·voip·freeswitch·sip·ippbx
北京盛世宏博1 小时前
异地机房采集踩坑笔记:以太网温湿度记录仪跨网段通信调试实战
开发语言·笔记·单片机·php·传感器·温湿度
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: Docker 数据卷命令与挂载实操
java·开发语言·docker
吴声子夜歌1 小时前
Docker入门与实战——实战案例(编程开发)(一)
运维·docker·容器
云和数据.ChenGuang1 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整指南
java·人工智能·spring boot·后端·重构·springai
霸道流氓气质1 小时前
AI 应用异常检测与根因分析:语义漂移检测、因果图推理与LLM-as-Judge的 Java 生产级实现
java·开发语言·人工智能
棉猴2 小时前
玩游戏学Python2-IDLE与pgzero入门
开发语言·python·游戏·玩游戏·pgzero·pgzrun
xuxigifxfh2 小时前
牛客:BISHI2 【模板】栈的操作
java·开发语言·windows·算法
房开民2 小时前
通俗总结 Octree(八叉树)
java·服务器·数据库