Skip to content
60.Docker»60.CNB下的Docker应用»LV010-自定义镜像.md

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 容器,这里使用最新版本,我们执行以下命令:

shell
docker run -it -d -p 8000:8000 ubuntu
docker ps
docker images

【例】:

shell
  /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. 更新软件包

我们刚才已经启动了容器,然后我们进入容器的终端,并更新软件包:

shell
docker exec -it <CONTAINER ID> /bin/bash
apt update

【例】:

shell
  /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 工具:

shell
apt install curl -y

安装完毕后,我们执行下面的命令安装:

shell
curl -fsSL https://code-server.dev/install.sh | sh

image-20251012185042555

到这里就安装完成了。

4. 运行 code-server

我们来运行看一下:

shell
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,所以这里我们修改一下监听端口,用下面的命令重新启动:

shell
code-server --bind-addr=0.0.0.0:8000 --auth=none

因为 code-server 启动时需要一个临时密码这里再添加一个 --auth=none 来禁用密码认证。然后我们点击监听地址就可以直接启动了:

image-20251012185226650

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

image-20251012185344539

然后就可以打开这个容器中的指定目录,访问相关文件了,比如图中,我们打开了根目录,我们在云原生开发环境的容器中创建一个 readme.md 文件:

shell
echo "test" > readme.md
cat readme.md # 查看 test.md 文件内容,会看到 test 被打印出来

然后就可以在容器对应的 code-server 中看到这个文件了:

image-20251012185709059

三、镜像制作

我们上面从一个纯净的 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
shell
# 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)暂停容器运行
shell
# 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 镜像。
shell
docker commit <容器ID> <镜像>:<>
# 例 docker commit 8a3c15164d45 docker-demo: 1.0.0

【例】

shell
# 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(可以先停止之前的容器,不然端口会冲突,或者换一个端口也行):
shell
docker stop <CONTAINER ID>
docker run -it -p 8000:8000 docker-demo:1.0.0 /bin/bash

【例】

shell
  /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:也可以一个命令直接启动:

shell
docker 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 重新启动:

shell
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 表示用什么命令去启动容器。

【例】

image-20251012191253007

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

image-20251012191443731

然后点击 open in Browser

image-20251012191530167

然后就能访问了。我们通过下面的命令可以看到刚才的容器的实时日志:

shell
docker logs <CONTAINER ID>

【例】

shell
  /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

Dockerfile 概述 | Docker 中文文档(Docker 官方文档,Docker 官方教程)

2.1 简介

Dockerfile 是一个文本文件,包含了构建 Docker 镜像的所有指令。文本内容包含了一条条构建镜像所需的指令和说明。它通过定义一系列命令和参数,指导 Docker 构建一个自定义的镜像。

image-20251013230214813

2.2 关键词语法

Dockerfile 参考 | Docker中文文档(Docker官方文档,Docker官方教程)

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

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

2.4 构建镜像

2.4.1 docker build

构建镜像时要使用 build 命令,通过读取 Dockerfile 中定义的指令,逐步构建镜像,并将最终结果保存到本地镜像库中。

shell
docker build [OPTIONS] PATH | URL | -

参数说明

  • PATH: 包含 Dockerfile 的目录路径或 .(当前目录)。

OPTIONS

  • -t, --tag: 为构建的镜像指定名称和标签。
  • -f, --file: 指定 Dockerfile 的路径(默认是 PATH 下的 Dockerfile)。
2.4.2 -t参数说明

这个-t参数是指定镜像名称和标签,后面要是推送到CNB的制品库的话,要参考一下这里:Docker 制品库 | CNB 文档

shell
# 同名制品
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 这个仓库,就直接以这个仓库名为镜像名:

shell
# docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}/<image_name>: latest .
docker build -t W3C/sumu/docker-demo:V1.0.1 .

Tips: 这里其实踩了坑,后面会提到,不踩坑的话可以用下面的镜像名:

shell
docker build -t docker.cnb.cool/w3c/sumu/docker-demo:1.0.0 .

image-20251012193505638

因为一开始我吧注释写在命令后面了,所以出现了报错,注释和命令要分两行写,然后重新执行就可以了,构建完毕后我们看一下镜像:

shell
  /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 的镜像。然后可以通过以下命令创建容器验证一下:

shell
  /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_feistel

2.5 镜像标签

想要上传到制品库或者其他docker仓库,我们的镜像需要一个tag,前面构建镜像的时候可以指定,但是我们前面没指定tag呢?可以用下面的命令:

shell
# 通过镜像名和标签
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 或私有注册表)。这使得镜像可以在其他系统或环境中共享和使用。

shell
docker push [OPTIONS] NAME[:TAG]

参数说明

  • NAME: 镜像名称,通常包含注册表地址(如 docker.io/myrepo/myimage)。
  • TAG(可选): 镜像标签,默认为 latest

OPTIONS

  • **--disable-content-trust 😗*忽略镜像的校验,默认开启
2.6.2 推送到CNB制品库

我们执行下面的命令:

shell
  /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

然后就报错了,这是因为没有添加制品库的链接地址,我们重新执行:

shell
  /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

这里是说我们的仓库名称必须都是小写,我们通过以下命令修改镜像名称:

shell
  /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

直接把制品库的前缀加到名称中,然后我们删除之前的就可以了,然后我们重新推送:

shell
  /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 看到这个镜像啦:

image-20251012194902493

2.7 镜像测试

我们本地删除镜像和容器:

shell
  /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 到制品库的镜像了,我们执行下面的命令:

shell
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

然后就可以正常在后台启动这个镜像创建的容器了:

image-20251012195513660

Tips:制品库镜像地址可以在对应的页面获取到:

image-20251012195338199

2.8 code-server 插件

我们还可以安装一些插件,我们修改 Dockerfile 如下:

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

然后重新制作镜像并创建容器测试:

shell
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

image-20251012203743100

会发现扩展都安装了,但是没有启用,我们需要配置下 /root/.local/share/code-server/User/settings.json

json
{
  "workbench.colorTheme": "One Dark Pro",
  "workbench.iconTheme": "material-icon-theme"
}

我们可以直接创建一个 settings.json 文件,添加这些内容,然后在 dockerfile 中拷贝到镜像中:

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
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
go
module golang_sample

go 1.23.3
3.2.3 Dockerfile.single
dockerfile
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
dockerfile
# 第一阶段:构建阶段
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 镜像构建

shell
docker build -t golang-demo-single -f Dockerfile.single .
docker build -t golang-demo-multi -f Dockerfile.multi .

构建完毕后使用下面的命令查看镜像:

shell
docker images -a

会看到以下信息:

shell
  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 运行容器

我们运行上面的两个镜像创建的容器,验证一下功能。

shell
docker run -d -p 8080:8080 golang-demo-single
docker run -d -p 8081:8081 golang-demo-multi

容器运行成功后可以通过如下命令行来访问,可以看到两个容器都是在运行我们写的 golang 服务。

shell
curl http://localhost:8080
curl http://localhost:8081

会得到以下信息:

shell
  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) 

参考资料:

code-server:浏览器上远程运行的 Visual Studio Code 的搭建与使用 - 滕王阁

莫道桑榆晚 为霞尚满天.