1. 目标与背景
目标:不使用docker build/ Dockerfile,而是手动组装一个 Docker 镜像 tar 包,再通过docker load 导入 Docker 引擎,使其可以运行容器。
最终要得到:
- 镜像名:
basic:v1 - 容器可运行
/bin/bash,并能执行命令:docker run --rm basic:v1 -c "echo hello"
2. 手动构建镜像的原理(必须理解)
Docker 镜像导入(docker load)本质上需要 3 个组成部分:
- Layers(层):一个或多个 tar 文件(这里是
demo-image.tar),包含 rootfs 文件系统内容 - config.json:镜像配置/元数据(Entrypoint、Env、WorkingDir、rootfs.diff_ids 等)
- manifest.json:镜像清单(RepoTags、使用哪些 Layers、对应哪个 config)
最后把它们一起打成一个basic.tar,交给docker load 导入。
3. Step-by-step 手动构建流程
Step 1:构建 rootfs 目录结构
创建最小文件系统目录:
mkdir -p rootfs/{dev,bin,lib64}
说明:
bin:放可执行文件(bash、echo)lib64:放动态库依赖(so)和动态链接器dev:设备目录(最小化镜像里通常先预留)
企业里通常还会补齐
/proc /sys /tmp /etc等目录,但你这个最小案例可不强制。
Step 2:拷贝可执行程序到 rootfs
先确认依赖(查看动态库):
ldd /bin/echo ldd /bin/bash
把程序放进 rootfs:
cp /bin/echo rootfs/bin/ cp /bin/bash rootfs/bin/
Step 3:拷贝动态库依赖与动态链接器(关键点)
/bin/bash、/bin/echo 是动态链接程序,需要系统动态库支持,否则容器启动会报:
exec /bin/bash: no such file or directory
这个错误很多时候不是 bash 不存在,而是动态链接器缺失导致 ELF 无法加载。
拷贝核心依赖: cp /lib64/libtinfo.so.6 rootfs/lib64/ cp /lib64/libdl.so.2 rootfs/lib64/ cp /lib64/libc.so.6 rootfs/lib64/ cp /lib64/ld-linux-x86-64.so.2 rootfs/lib64/
快速拷贝某程序依赖库(for 循环)
适用于像/bin/echo 这种依赖较少的程序:
for i in $(ldd /bin/echo | awk '{print $3}' | grep -v '^$'); do cp $i rootfs/lib64/ done
建议:对 bash 也可以做同样处理,但要注意
ldd输出里有些行不是库路径,需要过滤(比如linux-vdso.so.1)。
Step 4:打包 rootfs 成为镜像 layer(demo-image.tar)
打包命令:
tar -cf demo-image.tar -C rootfs/ .
参数解释:
-c:create 创建 tar 包-f demo-image.tar:指定包名-C rootfs/:切换到 rootfs 目录下再打包.:打包 rootfs 目录下所有内容(包括隐藏文件)
⚠️ 关键点: 必须用**-C rootfs .**,让 tar 解包后直接落到/bin /lib64 ...,不能把 rootfs 目录本身打进去,否则会变成/rootfs/bin/bash,容器会找不到/bin/bash。
Step 5:计算 layer 的 sha256(写入 config.json)
sha256sum demo-image.tar
得到一串 hash,例如:
3653d761aea229215c2fb5a8049dae704b699b28ac00c3bbbecff092c05f4a5d
Step 6:编写 config.json(镜像元数据)
创建config.json,内容如下(你示例):
{
"architecture": "amd64",
"os": "linux",
"config": {
"Entrypoint": ["/bin/bash"],
"Env": ["PATH=/bin"],
"WorkingDir": "/"
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:3653d761aea229215c2fb5a8049dae704b699b28ac00c3bbbecff092c05f4a5d"
]
},
"history": [
{
"created": "2024-06-18T00:00:00Z",
"created_by": "yai_yang"
}
]
}
字段解释(笔记重点):
architecture/os:镜像平台(amd64/linux)Entrypoint:容器默认启动程序,这里指定/bin/bashEnv:环境变量(这里只放 PATH=/bin)WorkingDir:默认工作目录/rootfs.diff_ids:镜像层的 hash 列表(这里是 demo-image.tar 的 sha)
Step 7:编写 manifest.json(镜像清单)
创建manifest.json:
[
{
"Config": "config.json",
"RepoTags": ["basic:v1"],
"Layers": ["demo-image.tar"]
}
]
字段解释:
Config:镜像配置文件名RepoTags:镜像名和 tag(导入后docker images可见)Layers:镜像包含的层 tar(这里仅一层 demo-image.tar)
Step 8:打包最终镜像 tar(basic.tar)
把 layer + config + manifest 打包成 Docker 可 load 的 tar:
tar -cf basic.tar demo-image.tar config.json manifest.json
Step 9:导入镜像
docker load -i basic.tar
导入后可用:
docker images | grep basic
Step 10:运行验证
因为 Entrypoint 是/bin/bash,所以可以直接执行 bash 的-c 参数:
docker run --rm basic:v1 -c "echo hello"
输出:
hello
说明镜像构建成功 ✅