LV010-自定义镜像
这一部分我们来自定义一个自己的镜像,进一步了解 docker。
一、概述
1. 云原生开发环境
我们在 CNB 上点击云原生开发,会快速启动一个开发环境,它其实也是一个 docker 镜像容器,我们可以参考 默认开发环境 | CNB 文档,默认镜像,这里就先不多说了。
我们这里来复刻一个 CNB 的云原生开发环境,这个开发环境其实是 linux 系统,然后里面安装了一个网页版的 vscode,还有一些其它工具,接下来我们就来做一个类似的镜像。
2. code-server
云端 vscode 的概念就是在浏览器中有和本地一样体验的 vscode 环境。在以下这个网站中,官方已经有实现在浏览器中的实现。
但是呢这个网站中 vscode 无法远程连接到远程服务器,那想要远程服务器,还有什么办法?
Github 仓库是这个:coder/code-server: VS Code in the browser
code-server 是服务端实现 vscode 的服务,只要服务器部署并运行运行这个服务,外部就可以访问相应的端口使用 vscode。这样我们就可以实现在任何计算机上、任何位置运行 VSCode,并且在浏览器中访问它。
二、部署 code-server
1. 启动 ubuntu 容器
我们先启动一个干净的 ubuntu 容器,这里使用最新版本,我们执行以下命令:
docker run -it -d -p 8000:8000 ubuntu
docker ps
docker images【例】:
➜ /workspace git:(main) docker run -it -d -p 8000:8000 ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
4b3ffd8ccb52: Pull complete
Digest: sha256:59a458b76b4e8896031cd559576eac7d6cb53a69b38ba819fb26518536368d86
Status: Downloaded newer image for ubuntu:latest
8a3c15164d450c472ca352c43a941e57ece5d9af4aa8577407477a9bec13c332
➜ /workspace git:(main) docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a3c15164d45 ubuntu "/bin/bash" 9 seconds ago Up 8 seconds 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp jovial_wilson
➜ /workspace git:(main) docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 97bed23a3497 10 days ago 78.1MB可以看到一开始拉取的 ubuntu 镜像是 78.1MB。后面我们要通过浏览器访问,所以这里直接加上 -p 参数进行端口映射,不然后面只能停止这个容器,然后重启的时候添加 -p 参数了。
端口映射(Port Mapping)是 Docker 中非常重要的功能,它的主要作用是将容器内部的端口与宿主机的端口进行绑定,使得外部可以通过宿主机的端口访问容器内部的服务。
2. 更新软件包
我们刚才已经启动了容器,然后我们进入容器的终端,并更新软件包:
docker exec -it <CONTAINER ID> /bin/bash
apt update【例】:
➜ /workspace git:(main) docker exec -it 8a3c15164d45 /bin/bash
root@8a3c15164d45:/# apt update
Get:1 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]
Get:2 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
# ......3. 部署 code-server
这里直接参考 coder/code-server: VS Code in the browser,我们按照 README.md 文档指示进行操作,先安装 curl 工具:
apt install curl -y安装完毕后,我们执行下面的命令安装:
curl -fsSL https://code-server.dev/install.sh | sh
到这里就安装完成了。
4. 运行 code-server
我们来运行看一下:
root@8a3c15164d45:/# code-server
[2025-10-12T10:50:50.468Z] info Wrote default config file to /root/.config/code-server/config.yaml
[2025-10-12T10:50:50.770Z] info code-server 4.104.3 cd40509fbbc684c5e9b566d9cb027f6ee9df36a6
[2025-10-12T10:50:50.770Z] info Using user-data-dir /root/.local/share/code-server
[2025-10-12T10:50:50.779Z] info Using config file /root/.config/code-server/config.yaml
[2025-10-12T10:50:50.779Z] info HTTP server listening on http://127.0.0.1:8080/
[2025-10-12T10:50:50.780Z] info - Authentication is enabled
[2025-10-12T10:50:50.780Z] info - Using password from /root/.config/code-server/config.yaml
[2025-10-12T10:50:50.780Z] info - Not serving HTTPS
[2025-10-12T10:50:50.780Z] info Session server listening on /root/.local/share/code-server/code-server-ipc.sock会发现监听端口是 8080,这个其实我们在宿主机是访问不到的,前面我们映射的端口是 8000,所以这里我们修改一下监听端口,用下面的命令重新启动:
code-server --bind-addr=0.0.0.0:8000 --auth=none因为 code-server 启动时需要一个临时密码这里再添加一个 --auth=none 来禁用密码认证。然后我们点击监听地址就可以直接启动了:

然后就可以打开我们用容器启动的 code-server 了:

然后就可以打开这个容器中的指定目录,访问相关文件了,比如图中,我们打开了根目录,我们在云原生开发环境的容器中创建一个 readme.md 文件:
echo "test" > readme.md
cat readme.md # 查看 test.md 文件内容,会看到 test 被打印出来然后就可以在容器对应的 code-server 中看到这个文件了:

三、镜像制作
我们上面从一个纯净的 ubuntu 镜像创建容器,然后在容器安装 code-server,每次我们启动都要这样的话就很繁琐了,我们可以直接做一个这样的镜像,当通过这个镜像启动容器的时候就直接包含了 code-server。Docker 构建镜像的方式有多种,最常用的两种
- 通过
docker commit命令,基于一个已存在的容器构建出镜像。 - 编写
Dockerfile文件,并使用docker build命令来构建镜像。
1. docker commit
1.1 命令说明
docker commit 命令用于将容器的当前状态保存为一个新的 Docker 镜像。
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]参数:
- CONTAINER:容器 ID
- REPOSITORY:指定镜像名称
- TAG:标签
OPTIONS:
- -a : 提交的镜像作者。
- -c : 使用 Dockerfile 指令来创建镜像。
- -m : 提交时的说明文字。
- -p : 提交镜像前暂停容器(默认为 true)。
1.2 使用示例
1.2.1 创建镜像
- (1)获取需要构建镜像的容器 ID
# docker ps
➜ /workspace git:(main) docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a3c15164d45 ubuntu "/bin/bash" 21 minutes ago Up About a minute 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp jovial_wilson- (2)暂停容器运行
# docker pause <CONTAINER ID>
➜ /workspace git:(main) docker pause 8a3c15164d45
8a3c15164d45
➜ /workspace git:(main) docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a3c15164d45 ubuntu "/bin/bash" 21 minutes ago Up About a minute (Paused) 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp jovial_wilson- (3)基于容器 ID 构建 Docker 镜像。
docker commit <容器ID> <镜像名>:<标签>
# 例 docker commit 8a3c15164d45 docker-demo: 1.0.0【例】
# docker commit <容器ID> <镜像名>: <标签>
➜ /workspace git:(main) docker commit 8a3c15164d45 docker-demo:1.0.0
sha256:dba94501b4e2282c4e85b7761de66d3506dd76fe88c6b480246f285a9d62e311
➜ /workspace git:(main) docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 1.0.0 dba94501b4e2 9 seconds ago 722MB
ubuntu latest 97bed23a3497 10 days ago 78.1MB可以看到本地多了一个镜像 docker-demo,标签为 1.0.0,大小是 722MB。
1.2.2 容器测试
- (4)创建容器,我们通过下面的命令使用刚才的镜像创建容器并启动 code-server(可以先停止之前的容器,不然端口会冲突,或者换一个端口也行):
docker stop <CONTAINER ID>
docker run -it -p 8000:8000 docker-demo:1.0.0 /bin/bash【例】
➜ /workspace git:(main) docker stop 8a3c15164d45
8a3c15164d45
➜ /workspace git:(main) docker run -it -p 8000:8000 docker-demo:1.0.0 /bin/bash
root@036d6dc047ad:/# code-server -v
4.104.3 cd40509fbbc684c5e9b566d9cb027f6ee9df36a6 with Code 1.104.3
root@036d6dc047ad:/# code-server --bind-addr = 0.0.0.0:8000 --auth = none
[2025-10-12T11:08:22.236Z] info code-server 4.104.3 cd40509fbbc684c5e9b566d9cb027f6ee9df36a6
[2025-10-12T11:08:22.236Z] info Using user-data-dir /root/.local/share/code-server
[2025-10-12T11:08:22.246Z] info Using config file /root/.config/code-server/config.yaml
[2025-10-12T11:08:22.246Z] info HTTP server listening on http://0.0.0.0:8000/
[2025-10-12T11:08:22.246Z] info - Authentication is disabled
[2025-10-12T11:08:22.246Z] info - Not serving HTTPS
[2025-10-12T11:08:22.246Z] info Session server listening on /root/.local/share/code-server/code-server-ipc.sock然后就可以在浏览器正常打开在容器中运行的这个 code-server 了。
Tips:也可以一个命令直接启动:
shelldocker run -it -p 8000:8000 docker-demo:1.0.0 code-server --bind-addr=0.0.0.0:8000 --auth=none
1.2.3 后台启动
刚才我们都是在前台启动的 code-server,这个样子的话,是不是也可以放到后台?我们停止刚才的容器,然后加上 --entrypoint 重新启动:
docker run -it -p 8000:8000 --entrypoint "code-server" -d docker-demo:1.0.0 --bind-addr=0.0.0.0:8000 --auth=none这里的 --entrypoint 表示用什么命令去启动容器。
【例】

可以看到已经启动了,我们可以访问一下,这次没有那个链接了,我们来添加一个端口:

然后点击 open in Browser:

然后就能访问了。我们通过下面的命令可以看到刚才的容器的实时日志:
docker logs <CONTAINER ID>【例】
➜ /workspace git:(main) docker logs 18a452514b4d
[2025-10-12T11:12:27.801Z] info code-server 4.104.3 cd40509fbbc684c5e9b566d9cb027f6ee9df36a6
[2025-10-12T11:12:27.802Z] info Using user-data-dir /root/.local/share/code-server
[2025-10-12T11:12:27.875Z] info Using config file /root/.config/code-server/config.yaml
[2025-10-12T11:12:27.875Z] info HTTP server listening on http://0.0.0.0:8000/
[2025-10-12T11:12:27.875Z] info - Authentication is disabled
[2025-10-12T11:12:27.875Z] info - Not serving HTTPS
[2025-10-12T11:12:27.875Z] info Session server listening on /root/.local/share/code-server/code-server-ipc.sock
[11:12:53]
[11:12:54] Extension host agent started.
[11:12:54] [172.21.7.1][4d51fed6][ExtensionHostConnection] Unknown reconnection token (never seen).
[11:12:55] [172.21.7.1][4e378f28][ManagementConnection] Unknown reconnection token (never seen).
File not found: /usr/lib/code-server/lib/vscode/node_modules/vsda/rust/web/vsda_bg.wasm
File not found: /usr/lib/code-server/lib/vscode/node_modules/vsda/rust/web/vsda.js
[11:15:49] [172.21.7.1][3cdf6c01][ManagementConnection] New connection established.
[11:15:51] [172.21.7.1][a1cfb054][ExtensionHostConnection] New connection established.
[11:15:51] [172.21.7.1][a1cfb054][ExtensionHostConnection] <49> Launched Extension Host Process.
[11:16:02] [File Watcher ('parcel')] Inotify limit reached (ENOSPC) (path: /)1.3 适用场景
这种镜像构建方式通常用在下面两个场景中:
构建临时的测试镜像;
容器被入侵后,使用 docker commit,基于被入侵的容器构建镜像,从而保留现场,方便以后追溯。
除了这两种场景,不建议使用 docker commit 来构建生产现网环境的镜像。主要原因有两个:
(1)使用 docker commit 构建的镜像包含了编译构建、安装软件,以及程序运行产生的大量无用文件,这会导致镜像体积很大,非常臃肿。
(2)使用 docker commit 构建的镜像会丢失掉所有对该镜像的操作历史,无法还原镜像的构建过程,不利于镜像的维护。
2. Dockerfile
2.1 简介
Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。文本内容包含了一条条构建镜像所需的指令和说明。它通过定义一系列命令和参数,指导 Docker 构建一个自定义的镜像。

2.2 关键词语法
2.2.1 常用关键词
| 指令 | 描述 |
|---|---|
| FROM | 构建新镜像是基于哪个镜像 |
| LABEL | 标签 (LABEL 打标签,写不写都行的;) |
| RUN | 构建镜像时运行的 Shell 命令 (通过 换行符:&& \,可以运行多行 shell 命令) |
| COPY | 拷贝文件或目录到镜像中 |
| ADD | 解压压缩包并拷贝 |
| ENV | 设置环境变量 |
| USER | 为 RUN、CMD 和 ENTRYPOINT 执行命令指定运行用户 |
| EXPOSE | 声明容器运行的服务端口(EXPOSE 80 :事声明一个 80 端口,不是实际暴露;) 声明容器监听的端口号为 12445。这主要是为了文档和开发目的,实际暴露端口是在运行容器时通过 docker run -p 参数来实现的。 |
| WORKDIR | 为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录; |
| CMD | 运行容器时默认执行,如果有多个 CMD 指令,最后一个生效。 |
2.2.2 Dockerfile实例
我们在代码仓库创建 Dockerfile 文件,使用最新版ubuntu作为基础镜像,然后安装curl,最后再安装code-server
# 基础镜像使用最新版本ubuntu
FROM ubuntu:latest
# 更新软件包
RUN apt-get update
# 安装curl
RUN apt-get install -y curl
# 安装code-server
RUN curl -fsSL https://code-server.dev/install.sh | sh2.4 构建镜像
2.4.1 docker build
构建镜像时要使用 build 命令,通过读取 Dockerfile 中定义的指令,逐步构建镜像,并将最终结果保存到本地镜像库中。
docker build [OPTIONS] PATH | URL | -参数说明:
PATH: 包含 Dockerfile 的目录路径或.(当前目录)。
OPTIONS:
-t, --tag: 为构建的镜像指定名称和标签。-f, --file: 指定 Dockerfile 的路径(默认是PATH下的Dockerfile)。
2.4.2 -t参数说明
这个-t参数是指定镜像名称和标签,后面要是推送到CNB的制品库的话,要参考一下这里:Docker 制品库 | CNB 文档
# 同名制品
docker build -t docker.cnb.cool/{repository-path}:latest .
# 非同名制品
docker build -t docker.cnb.cool/{repository-path}/{image-name}:latest .2.4.3 镜像构建实例
我们来尝试一下,这里用 -t 指定镜像名,我们使用的是 W3C/ sumu/ docker-demo 这个仓库,就直接以这个仓库名为镜像名:
# docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/<image_name>: latest .
docker build -t W3C/sumu/docker-demo:V1.0.1 .Tips: 这里其实踩了坑,后面会提到,不踩坑的话可以用下面的镜像名:
shelldocker build -t docker.cnb.cool/w3c/sumu/docker-demo:1.0.0 .

因为一开始我吧注释写在命令后面了,所以出现了报错,注释和命令要分两行写,然后重新执行就可以了,构建完毕后我们看一下镜像:
➜ /workspace git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
W3C/sumu/docker-demo V1.0.1 f727e275acd6 About a minute ago 721MB
docker-demo 1.0.0 dba94501b4e2 31 minutes ago 722MB
ubuntu latest 97bed23a3497 10 days ago 78.1MB会看到出现了一个名为 W3C/sumu/docker-demo,标签为 V1.0.1 的镜像。然后可以通过以下命令创建容器验证一下:
➜ /workspace git:(main) ✗ docker run -it -p 8000:8000 --entrypoint "code-server" -d W3C/sumu/docker-demo:V1.0.1 --bind-addr=0.0.0.0:8000 --auth=none
d5be62967eaa1b9e7e1061c071df8cc7285ad4797207c83866c83fde674d5134
➜ /workspace git:(main) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d5be62967eaa W3C/sumu/docker-demo:V1.0.1 "code-server --bind-…" 11 seconds ago Up 10 seconds 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp kind_feistel2.5 镜像标签
想要上传到制品库或者其他docker仓库,我们的镜像需要一个tag,前面构建镜像的时候可以指定,但是我们前面没指定tag呢?可以用下面的命令:
# 通过镜像名和标签
docker tag my-app:latest my-app:v1.0
# 通过镜像ID
docker tag afbd7a563542 my-app:stable后面推送到CNB制品库的花,要注意镜像标签的格式。
2.6 推送到制品库
2.6.1 docker push
因为我们是在 cnb 云原生开发环境中创建的镜像,我们可以直接推送这个镜像到当前仓库的制品库中。docker push 命令用于将本地构建的 Docker 镜像推送(上传)到 Docker 注册表(如 Docker Hub 或私有注册表)。这使得镜像可以在其他系统或环境中共享和使用。
docker push [OPTIONS] NAME[:TAG]参数说明:
NAME: 镜像名称,通常包含注册表地址(如docker.io/myrepo/myimage)。TAG(可选): 镜像标签,默认为latest。
OPTIONS:
- **--disable-content-trust 😗*忽略镜像的校验,默认开启
2.6.2 推送到CNB制品库
我们执行下面的命令:
➜ /workspace git:(main) ✗ docker push W3C/sumu/docker-demo:V1.0.1
The push refers to repository [W3C/sumu/docker-demo]
Get "https://W3C/v2/": dial tcp: lookup W3C on 127.0.0.11:53: no such host然后就报错了,这是因为没有添加制品库的链接地址,我们重新执行:
➜ /workspace git:(main) ✗ docker push docker.cnb.cool/W3C/sumu/docker-demo:V1.0.1
invalid reference format: repository name (W3C/sumu/docker-demo) must be lowercase这里是说我们的仓库名称必须都是小写,我们通过以下命令修改镜像名称:
➜ /workspace git:(main) ✗ docker tag W3C/sumu/docker-demo:V1.0.1 docker.cnb.cool/w3c/sumu/docker-demo:1.0.0
➜ /workspace git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
W3C/sumu/docker-demo V1.0.1 f727e275acd6 11 minutes ago 721MB
docker.cnb.cool/w3c/sumu/docker-demo 1.0.0 f727e275acd6 11 minutes ago 721MB
docker-demo 1.0.0 dba94501b4e2 40 minutes ago 722MB
ubuntu latest 97bed23a3497 10 days ago 78.1MB直接把制品库的前缀加到名称中,然后我们删除之前的就可以了,然后我们重新推送:
➜ /workspace git:(main) ✗ docker push docker.cnb.cool/w3c/sumu/docker-demo:1.0.0
The push refers to repository [docker.cnb.cool/w3c/sumu/docker-demo]
0ba09f29aab5: Pushed
c40df67c26b0: Pushed
6c36a1c6aeae: Pushed
073ec47a8c22: Pushed
1.0.0: digest: sha256:6f6ebdc4ae4609efd3c36f110042931f49929a3053c1ea15c6546151671ded0f size: 1165然后我们就可以在这里 制品 · W3C/sumu/docker-demo 看到这个镜像啦:

2.7 镜像测试
我们本地删除镜像和容器:
➜ /workspace git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 1.0.0 dba94501b4e2 47 minutes ago 722MB
ubuntu latest 97bed23a3497 10 days ago 78.1MB可以看到本地已经没有刚才 push 到制品库的镜像了,我们执行下面的命令:
docker run -it -p 8000:8000 --entrypoint "code-server" -d docker.cnb.cool/w3c/sumu/docker-demo:1.0.0 --bind-addr=0.0.0.0:8000 --auth=none然后就可以正常在后台启动这个镜像创建的容器了:

Tips:制品库镜像地址可以在对应的页面获取到:
2.8 code-server 插件
我们还可以安装一些插件,我们修改 Dockerfile 如下:
# 基础镜像使用最新版本ubuntu
FROM ubuntu:latest
# 更新软件包
RUN apt-get update
# 安装curl
RUN apt-get install -y curl
# 安装code-server
RUN curl -fsSL https://code-server.dev/install.sh | sh
RUN code-server --install-extension zhuangtongfa.material-theme &&\
code-server --install-extension PKief.material-icon-theme然后重新制作镜像并创建容器测试:
docker build -t docker-demo:1.0.1 .
docker run -it -p 8000:8000 --entrypoint "code-server" -d docker-demo:1.0.1 --bind-addr=0.0.0.0:8000 --auth=none
会发现扩展都安装了,但是没有启用,我们需要配置下 /root/.local/share/code-server/User/settings.json:
{
"workbench.colorTheme": "One Dark Pro",
"workbench.iconTheme": "material-icon-theme"
}我们可以直接创建一个 settings.json 文件,添加这些内容,然后在 dockerfile 中拷贝到镜像中:
COPY settings.json /root/.local/share/code-server/User/settings.json然后一打开页面就会开启对应的配置项了。
四、多阶段构建
1. 什么是多阶段构建
多阶段构建允许在一个 Dockerfile 中使用多个 FROM 指令,每个阶段都可以有不同的基础镜像。这意味着你可以在不同的阶段使用不同的工具、库和环境,最终只保留运行应用所需的部分。这种方法不仅提高了效率,还能确保最终镜像的安全性和简洁性。
多阶段构建的优势:
- 最终镜像只包含运行时必需的文件
- 不包含源代码和构建工具,提高了安全性
- 大大减小了镜像体积,节省存储空间和网络带宽
2. 基本原理
FROM指令开始一个新的构建阶段,设置后续构建依赖的基础镜像,Dockerfile必须以FROM开始,最终镜像只包含最后一个FROM指令开始的阶段所构建的内容。镜像可以是任意有效镜像。可以在一个Dockerfile中出现多次,以创建多个镜像或者将当前构建作为另一个构建的依赖。
总的来说就是因为每个 FROM 指令都开启一个全新的、独立的环境,所以我们可以只保留最后一个FROM阶段的内容在最终镜像中,需要注意的是每个 FROM 都是全新的文件系统,需要显式复制文件。只有通过 COPY --from 显式复制的文件才会进入最终镜像。
所以,对于一个多阶段构建来说,我们可以定义构建阶段,在构建阶段构建出我们需要的一些应用程序的成果物,然后直接在最终镜像中使用,而构建过程中安装的依赖、中间文件等在构建完成后就没有任何用处了,这样进行多阶段构建可以保证最终镜像中不包含这些中间产物,以此来减小最终镜像的体积。
3. 镜像构建实例
这里构建 golang 应用来作为对比。
3.1 文件准备
3.1.1 main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080" // 默认端口
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, Docker Build!")
})
log.Printf("Server starting on port %s...\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}3.1.2 go.mod
module golang_sample
go 1.23.33.2.3 Dockerfile.single
FROM golang:1.23
# 定义构建参数
ARG PORT=8080
# 设置环境变量
ENV PORT=${PORT}
WORKDIR /app
COPY go.mod .
COPY main.go .
ENV CGO_ENABLED=0
ENV GOOS=linux
RUN go mod tidy && go build -ldflags="-w -s" -o server .
EXPOSE ${PORT}
CMD ["./server"]3.2.4 Dockerfile.multi
# 第一阶段:构建阶段
FROM golang:1.23 AS builder
# 设置工作目录
WORKDIR /app
# 将源代码复制到容器中
COPY go.mod .
COPY main.go .
# 设置必要的 Go 环境变量
ENV CGO_ENABLED=0
ENV GOOS=linux
# 编译 Go 应用
RUN go mod tidy && go build -ldflags="-w -s" -o server .
# 第二阶段:运行阶段
FROM alpine:latest
# 定义构建参数
ARG PORT=8081
# 设置环境变量
ENV PORT=${PORT}
# 安装 CA 证书,这在某些需要 HTTPS 请求的应用中可能需要
RUN apk --no-cache add ca-certificates
# 设置工作目录
WORKDIR /root/
# 从 builder 阶段复制编译好的二进制文件
COPY --from=builder /app/server .
# 暴露应用端口
EXPOSE ${PORT}
# 运行应用
CMD ["./server"]3.2 镜像构建
docker build -t golang-demo-single -f Dockerfile.single .
docker build -t golang-demo-multi -f Dockerfile.multi .构建完毕后使用下面的命令查看镜像:
docker images -a会看到以下信息:
➜ 01-golang-sample git:(main) ✗ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
golang-demo-multi latest ede2b1fc2e60 11 seconds ago 13.9MB
golang-demo-single latest 221d7bedff58 About a minute ago 916MB可以看到多阶段构建的镜像明显小于单阶段构建的镜像。
3.3 运行容器
我们运行上面的两个镜像创建的容器,验证一下功能。
docker run -d -p 8080:8080 golang-demo-single
docker run -d -p 8081:8081 golang-demo-multi容器运行成功后可以通过如下命令行来访问,可以看到两个容器都是在运行我们写的 golang 服务。
curl http://localhost:8080
curl http://localhost:8081会得到以下信息:
➜ 01-golang-sample git:(main) ✗ curl http://localhost:8080
Hello, Docker Build!#
➜ 01-golang-sample git:(main) ✗ curl http://localhost:8081
Hello, Docker Build!#
➜ 01-golang-sample git:(main) ✗参考资料:
