Go 程序员的 Docker 基础操作指南
Docker 部署 Go 项目
一、分阶段构建
由于 go 的部署只需要编译后的可执行文件就可以了,并不需要 go 的环境。
因此可以先在有 go 环境的容器中对项目进行构建,得到构建后的可执行文件
然后再把这个可执行文件放到一个比较小的镜像容器里面。
1 准备一个 go 程序 和一个dockerfile
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(context *gin.Context) {
context.String(200, "OK")
})
r.Run(":8080")
}
FROM golang:alpine AS builder # 引入 go 的镜像,这样有 go 的环境
# 构建可执行文件
ENV CGO_ENABLED 0 # 关闭 cgo
ENV GOPROXY https://goproxy.cn,direct # 设置国内代理
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories # 镜像源地址改为阿里云,提升软件包下载速度
WORKDIR /build # 设置工作目录
ADD go.mod . # 将宿主机中的 go.mod 文件复制到容器的当前工作目录
ADD go.sum .
ADD main.go . # 将宿主机中的源文件复制到容器的工作目录下
RUN go build -o main # 在容器内执行编译,得到可执行文件名为 main
# 多阶段构建的第二阶段
FROM scratch # 以一个特殊空白镜像为基础
WORKDIR /app # 设置容器内的工作目录为 /app
COPY --from=builder /build/main /app # 前一阶段的执行文件复制到当前工作目录
CMD ["./main"] # 执行可执行程序
2 完整构建流程
| 阶段 | 作用 |
|---|---|
| 第一阶段(builder) | 基于 Go 镜像,拷贝源码 → 编译生成 main 二进制文件 |
| 第二阶段(最终镜像) | 基于 scratch **空白镜像,**只拷贝编译好的 main → 生成极小的运行镜像 |
以一个空白镜像,只把上一阶段编译好的二进制文件拷贝进来,最终生成的镜像只有几 MB 到 十几MB,非常轻量,适合生产环境部署
注意:多阶段构建中,最终镜像只包含最后一个 FROM 阶段的内容,之前的所有阶段的文件不会被带入最终镜像。
3 构建镜像的具体操作
项目结构
my-go-app/
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
在 Dockerfile 所在目录下执行
docker build -t my-go-app:latest .
-t my-go-app:latest:给镜像打标签,方便后续引用.:指定构建上下文为当前目录
运行容器
docker run -d -p 8080:8080 my-go-app:latest
-d后台运行-p 8080:8080将宿主机 8080 端口映射到容器的 8080 端口(假设 Go 程序监听 8080 端口)
最终生成的是一个 Docker 镜像(Image),它的特点是:
| 特性 | 说明 |
|---|---|
| 内容 | 只包含一个静态编译的 Go 可执行文件 |
| 大小 | 通常 5~20 MB(取决于你的程序) |
| 运行环境 | 无需安装 Go、无需任何依赖库 |
| 可移植性 | 可以在任何支持 Docker 的 Linux 机器上运行 |
二、运行时挂载注入配置文件
Dockerfile 不变,运行时通过**挂载卷 (Volume)**把配置文件注入容器
docker run -d -p 8088:8080 -v /host/path/config.yaml:/app/config.yaml my-go-app:latest
把宿主机绝对路径下的配置文件映射到镜像工作目录下
如果是配置中心
只需要在运行时把配置中心的地址等信息通过环境变量传进去
docker run -d -p 8080:8080 \
-e CONFIG_CENTER_ADDR=http://nanos.example.com:8848 \
-e CONFIG_CENTER_NAMESPACE=prod \
-e APP_NAME=my-go-app \
my-go-app:latest
Go 应用通过 os.Getenv("CONFIG_CENTER_ADDR") 就能读到这些值。
有一个极其关键的“坑”必须注意
-
如果配置中心地址是 http 不需要改 Dockerfile,容器内 Go 程序可以正常发起 http 请求
-
绝大多数生产环境的配置中心都强制开启 https,scratch 镜像里没有任何根证书(CA Certificates),Go 程序会因此拒绝连接。
解决方案:修改 Dockerfile,在构建阶段把根证书复制进 scratch 镜像
# 第一阶段:构建 FROM golang:1.21-alpine AS builder # ...(构建代码) # 第二阶段:scratch FROM scratch WORKDIR /app # 关键:从 Alpine 镜像中复制 CA 证书和时区文件(解决 TLS 和时区问题) COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=builder /build/main /app/ # 设置环境变量指定配置中心地址(代码里读取) ENV CONFIG_CENTER_URL="https://config-server:8848" CMD ["./main"]服务发现与 DNS 问题:
scratch虽然缺失很多文件,但 DNS 解析是依赖宿主机的/etc/resolv.conf的,Docker 默认会挂载宿主机的该文件,所以如果你的配置中心地址是域名(如nacos.dev.svc.cluster.local),Go 程序依然可以正常解析,无需额外处理。 -
如果配置中心依赖 gRPC,可能需要时区文件
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
三、设置 docker 网络连接外部服务
以 MySQL 为例,MySQL 可能在内网的服务器上,也可能单独起一个 MySQL 容器
第一种情况,因为 Docker 默认会将容器连接到宿主机的 Docker0 网桥上,容器和宿主机是可以直接通信的,所以直接连就行了
第二种情况,单独起 MySQL 容器和我们的项目容器是可以互相连接的
MySQL 在宿主机内网
只需通过环境变量把 IP 或内网域名传进去即可(如果没有配置文件的话)
docker run -d -p 8080:8080 \
-e DB_HOST=192.168.1.100 \
-e DB_PORT=3306 \
-e DB_USER=root \
-e DB_PASS=123456 \
-e DB_NAME=mydb \
my-go-app:latest
注意:如果 MySQL 在宿主机物理机上,且 Docker 使用默认桥接网络,千万不能用 localhost 或 127.0.0.1(因为容器内的 localhost 指向容器自己)。必须填宿主机的内网 IP 或特殊的 host.docker.internal(Mac/Windows 适用,Linux 需加 --add-host)。
MySQL 是另一个独立的 Docker 容器(同一台机器)
自定义一个 Docker 网络,让两个容器通过容器名互相访问
方式1:使用 docker run 手动管理
第一步:创建网络并启动 MySQL 容器
docker network create my-network
# 启动 MySQL 容器,加入该网络
docker run -d --name mysql-container --network my-network -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=mydb mysql:8.0
第二部:运行 Go 应用,加入同一网络
docker run -d -p 8080:8080 \
--network my-network \
-e DB_HOST=mysql-container \ # 关键:直接用容器名,Docker 内置 DNS 会解析
-e DB_PORT=3306 \
-e DB_USER=root \
-e DB_PASS=123456 \
my-go-app:latest
方式2:使用 Docker Compose
创建一个 docker-compose.yaml
version: '3.8'
services:
app:
# 挂载宿主机的 CA 证书 :ro read-only
volumes:
- ca-data:/etc/ssl/certs/ca-certificates.crt:ro
image: my-go-app:latest
build: .
ports:
- "8080:8080"
environment:
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=123456
# 定义服务依赖关系,确保 MySQL 服务先启动
depends_on:
- mysql
# 设置重启策略:容器退出时总是自动重启
restart: always
mysql:
# 指定镜像版本
image: mysql:8.0
# 设置环境变量
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=mydb
# 数据卷挂载:将宿主机的 ./mysql/data 目录挂载到容器的指定目录,实现数据持久化
volumes:
- mysql-data:/var/lib/mysql
# 自定义 MySQL 启动命令
command:
[
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci",
"--max_connections=3000",
]
# 设置重启策略:容器退出时总是自动重启
restart: always
volumes:
ca-data: /etc/ssl/certs/ca-certificates.crt
mysql-data: ./mysql/data
然后启动
docker compose up -d
Docker Compose 会自动创建默认网络,把app和mysql两个容器加入同一网络,容器之间可以通过服务名 mysql 互相访问。
但这里有一个需要注意的地方,MySQL 容器启动需要几秒钟,虽然 depends_on 让 Docker 先起 MySQL,但可能还没准备好,需要在 Go 中重试等待 MySQL 启动。
四、小结
本文档围绕「使用 scratch 空白镜像部署 Go 项目」这一核心思路,从镜像构建、配置管理、网络连接三个维度给出了完整的实践方案。核心要点总结如下:
| 维度 | 关键结论 |
|---|---|
| 镜像构建 | 采用多阶段构建,第一阶段在 golang:alpine 中编译出静态二进制文件,第二阶段复制到 scratch 中,最终镜像仅包含一个可执行文件,体积约 5~20 MB,极其轻量且安全。 |
| 配置文件 | 推荐运行时通过 -v 挂载配置文件,或通过 -e 注入环境变量;若使用配置中心,务必注意 HTTPS 场景下需在 Dockerfile 中复制 CA 证书,否则 scratch 无法验证 TLS 证书。 |
| 外部服务(如 MySQL) | 通过自定义 Docker 网络或 Docker Compose 实现容器间互联,使用**容器名(服务名)**作为 DB_HOST;必须注意 MySQL 启动耗时,Go 代码中应实现连接重试逻辑,避免容器启动即崩溃。 |
| Docker Compose | 使用 Compose 可大幅简化多容器编排,自动创建网络和服务发现;配合 healthcheck + condition: service_healthy 可确保依赖服务就绪后再启动应用。 |
一句话总结:FROM scratch 是构建极简 Go 生产镜像的最佳选择,但必须补齐 CA 证书(如需 HTTPS)、引入连接重试(如需依赖外部服务)这两项“能力补丁”,才能让它在生产环境中稳定运行。
更多推荐




所有评论(0)