在 Dockerfile 中以 scratch 为基础镜像 (FROM scratch)
通常使用 Docker 镜像时会以一个已存在的镜像为基础,在其上进行定制,这个已存在的镜像就是基础镜像。

DockerFile 中必须指定基础镜像,FROM 指令就是用于指定基础镜像,因此一个 Dockerfile 中FROM 是必备的指令,并且必须是第一条指令

Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。在 Dockerfile 中以 scratch 为基础镜像 (FROM scratch),意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

1. 创建基于静态编译的 C 程序镜像

本文在 Ubuntu 20.04 下创建基于静态编译的 C 程序镜像。
Docker 版本: 20.10.7
Docker Compose 版本: 2.6.1

1) C 程序

$ cd ~/gcc
$ vim hello.c

1
2
3
4
5
#include <stdio.h>
int main() {
puts("Hello World!- C");
return 0;
}

#gcc 静态编译
$gcc hello.c -static -o hello
$./hello

1
Hello World! - C

$ll -h hello

1
-rwxrwxr-x 1 root root   852K  hello

2) 创建 Dockerfile

$ cd ~/gcc
$ vim Dockerfile

1
2
3
FROM scratch
COPY hello /
CMD ["/hello"]

注:scratch 空镜像中没有 sh 或 bash,无法 mkdir、mv 等 shell 命令是无效的,因此需要在镜像外部把文件目录结构建立好,然后通过 ADD 或 COPY 命令拷贝到容器内。

3) 创建 hello 镜像,并运行容器

$ cd ~/gcc
#创建镜像
$ docker build -t hello:1.0 .

1
2
3
4
5
6
7
8
9
10
Step 1/3 : FROM scratch
--->
Step 2/3 : COPY hello /
---> bb893abeef08
Step 3/3 : CMD ["/hello"]
---> Running in c31e62693472
Removing intermediate container c31e62693472
---> cebea71dcbe0
Successfully built cebea71dcbe0
Successfully tagged hello:1.0

$ docker images

1
2
REPOSITORY      TAG      IMAGE ID       CREATED          SIZE
hello 1.0 cebea71dcbe0 38 seconds ago 872kB

$ docker run –rm hello:1.0

1
Hello World!- C

注:以上 Dockerfile 制作出来的镜像是 872kB,hello 的二进制文件是 852kB。使用 scratch 空镜像的本质是让程序只调用 host 主机的 Linux 内核部分的功能,而不依赖容器内的操作环境功能。host 主机的 Linux 内核部分对 Docker 容器是共享的,因此其 scratch 空镜像的大小可以认为近似为 0。

2. 创建基于编译的 Go 程序镜像

本文在 Ubuntu 20.04 下创建基于编译的 Go 程序镜像。
Docker 版本: 20.10.7
Docker Compose 版本: 2.6.1

1) Go 程序

$ cd ~/go
$ vim test.go

1
2
3
4
5
package main
import "fmt"
func main() {
fmt.Println("Hello world - Go")
}

#编译
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags ‘-w -s’ test.go

参数说明:
GOOS=linux GOARCH=amd64 表示确保编译出来的程序可以运行在 amd64 linux 环境;
CGO_ENABLED=0 表示确保用到的 C 函数库包含到 Go run-time 中,程序运行时以静态方式内部调用。否则,由于 scratch 空镜像内没有 C 函数库,Go 程序动态调用时会出错;
-ldflags ‘-w -s’ 表示排除 Debug 信息,让编译出来的程序更小。-w 是排除 DWARF,-s 是排除 debug symbol;
注:Go 语言调用 C 函数库出错的现象也会出现在 alpine 中,这是因为 alpine 的 C 函数库是精简版的。
$ ./test

1
Hello world - Go

$ ll -h test

1
-rwxrwxr-x 1 root root  1.2M  test

2) 创建 Dockerfile

$ cd ~/go
$ vim Dockerfile

1
2
3
FROM scratch
COPY test /
CMD ["/test"]

3) 创建 test 镜像,并运行容器

$ cd ~/go
#创建镜像
$ docker build -t test:1.0 .

1
2
3
4
5
6
7
8
9
10
Step 1/3 : FROM scratch
--->
Step 2/3 : COPY test /
---> cd67f4bfb544
Step 3/3 : CMD ["/test"]
---> Running in c3cf81ea01e4
Removing intermediate container c3cf81ea01e4
---> 535665c081c8
Successfully built 535665c081c8
Successfully tagged test:1.0

$ docker images

1
2
REPOSITORY      TAG      IMAGE ID       CREATED          SIZE
test 1.0 535665c081c8 29 seconds ago 1.18MB

$ docker run –rm test:1.0

1
Hello world - Go

3. 创建基于 Debian rootfs 的 Linux 镜像

由于 scratch 空镜像内,没有操作系统的根文件系统(rootfs),无法运行 sh 或 bash,无法进入容器内进行交互式调试。我们可以给基于 scratch 空镜像创建的镜像里,添加一个 rootfs。
Debian:https://www.debian.org/
Docker Debain: https://docker.debian.net/
Docker Debain GitHub: https://github.com/debuerreotype/docker-debian-artifacts

本文在 Ubuntu 20.04 下创建基于 Debian rootfs 的 Linux 镜像。
Docker 版本: 20.10.7
Docker Compose 版本: 2.6.1

1)下载 rootfs

这里选用了 https://docker.debian.net/ 页面上的 debian:bookworm-20230227,amd64 链接跳转到页面 https://github.com/debuerreotype/docker-debian-artifacts/tree/fe5738569aad49a97cf73183a8a6b2732fe57840/bookworm。
下载 rootfs.tar.xz 文件到 ~/debian 目录下,文件大小 29.66MB。

2) 创建 Dockerfile

$ cd ~/debian
$ vim Dockerfile

1
2
3
4
FROM scratch
Add rootfs.tar.xz /
WORKDIR /home/docker
CMD /bin/bash

3) 创建 Linux 镜像,并运行容器

$ cd ~/debian
#创建镜像
$ docker build -t debian_local:1.0 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Step 1/4 : FROM scratch
--->
Step 2/4 : Add rootfs.tar.xz /
---> 806b049c2199
Step 3/4 : WORKDIR /home/docker
---> Running in e4c0defe9fd3
Removing intermediate container e4c0defe9fd3
---> a2bea1387c68
Step 4/4 : CMD /bin/bash
---> Running in ea20508cb334
Removing intermediate container ea20508cb334
---> fd4fa7caba5d
Successfully built fd4fa7caba5d
Successfully tagged debian_local:1.0

$ docker images

1
2
REPOSITORY      TAG      IMAGE ID       CREATED          SIZE
debian_local 1.0 fd4fa7caba5d 38 seconds ago 116MB

$ docker run -itd –name debian-local-1.0 debian_local:1.0

1
0e6e3704225c5723349e8d1cc07fefefdf93d205cdabc6f938f3726482b0d918

$ docker exec -it debian-local-1.0 /bin/bash

1
2
3
4
5
root@0e6e3704225c:/home/docker# cd /
root@0e6e3704225c:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@0e6e3704225c:/# cat /etc/issue
Debian GNU/Linux bookworm/sid \n \l

4. 创建基于 CentOS rootfs 的 Linux 镜像

CentOS:https://www.centos.org/
CentOS Vault Mirror: https://vault.centos.org/
本文在 CentOS 7.9 下创建基于 CentOS rootfs 的 Linux 镜像。
Docker 版本: 20.10.7
Docker Compose 版本: 2.6.1

1) 制作 CentOS 7.5 rootfs

$ mkdir -p ~/centos75/rootfs
$ cd ~/centos75
$ rpm –root /home/xxx/centos75/rootfs –initdb # 设置 rpm 操作的根目录
#下载
$ wget https://vault.centos.org/7.5.1804/os/x86_64/Packages/centos-release-7-5.1804.el7.centos.x86_64.rpm
$ sudo rpm -ivh –nodeps –root /home/xxx/centos75/rootfs –package ./centos-release-7-5.1804.el7.centos.x86_64.rpm

1
2
3
4
5
warning: ./centos-release-7-5.1804.el7.centos.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Preparing... ################################# [100%]
Updating / installing...
1:centos-release-7-5.1804.el7.cento################################# [100%]
warning: %post(centos-release-7-5.1804.el7.centos.x86_64) scriptlet failed, exit status 127

$ sudo yum –installroot=/home/xxx/centos75/rootfs install yum –nogpgcheck

1
...

$ ls ./rootfs

1
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

2) 创建 Dockerfile

$ cd ~/centos75
$ vim Dockerfile

1
2
3
4
FROM scratch
Add ./rootfs /
WORKDIR /home/docker
CMD /bin/bash

3) 创建 Linux 镜像,并运行容器

$ cd ~/centos75
#创建镜像
$ sudo docker build -t centos_local:7.5 .

1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Building 10.1s (6/6) FINISHED
=> [internal] load build definition from ockerfile 0.0s
=> => transferring dockerfile: 101B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 4.4s
=> => transferring context: 453.26MB 4.4s
=> [1/2] ADD ./rootfs / 2.2s
=> [2/2] WORKDIR /home/docker 0.3s
=> exporting to image 3.1s
=> => exporting layers 3.1s
=> => writing image sha256:72826446cccfd5363073ca1923c59cabf843278aa82d20ad720ac2d1cf458333 0.0s
=> => naming to docker.io/library/centos_local:7.5 0.0s

$ docker images

1
2
REPOSITORY          TAG      IMAGE ID       CREATED          SIZE
centos_local 7.5 72826446cccf 4 minutes ago 452MB

$ docker run -itd –name centos-local-1.0 centos_local:7.5

1
3223f140df4d26e951dadf2d938fcc561f78ac7de86ca469e6c60853a03162fd

$ docker exec -it centos-local-1.0 /bin/bash

1
2
3
4
5
bash-4.2# cd /
bash-4.2# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
bash-4.2# cat /etc/centos-release
CentOS Linux release 7.5.1804 (Core)

参考文献或转载相关: