Golang后端: Docker多阶段构建详解与部署完整指南
前言
这周正式完成了go进阶终端简易版聊天室项目, 现在需要把后端代码部署到docker容器中运行起来, 于是写下这篇博客记录一下部署过程以及一些error
部署步骤
1. 先确定项目结构
这是我个人的开发项目结构:

没有做过多的分包处理, 毕竟是一个简易版的终端实现, 没有利用任何集成框架
2. 编写DockerFile和docker-compose.yaml文件
DockerFile
# 构建阶段
# 从官方的golang:1.23-alpine镜像开始构建
# alpine表示使用基于Alpine Linux的轻量级基础镜像
# AS builder 给这个构建阶段起一个名字叫builder,后续可以在其他阶段引用它
FROM golang:1.23-alpine AS builder
# 设置工作目录为 /app 后续所有命令(如 COPY、RUN)都会在这个目录下执行
WORKDIR /app
# 设置Go模块代理为国内的 https://goproxy.cn 加快依赖下载速度
# ,direct 表示如果代理无法获取某个模块,则直接连接源站
ENV GOPROXY=https://goproxy.cn,direct
# 将本地项目根目录下的go.mod和go.sum文件复制到容器中的当前工作目录(即 /app)
# 这样做是为了提前下载依赖,利用Docker的缓存机制:只要go.mod和go.sum没变,就不需要重新下载依赖
COPY go.mod go.sum ./
#下载项目所需的所有 Go 模块依赖到容器中
RUN go mod download
# 将本地项目根目录下的所有文件(包括源代码)复制到容器的 /app 目录中
COPY . .
# 编译 Go 程序:
# CGO_ENABLED=0:禁用 CGO,使得编译出的是纯静态二进制文件,不依赖系统动态库,更适合在 Alpine 等最小化系统中运行
# GOOS=linux:指定目标操作系统为 Linux
# GOARCH=amd64:指定目标 CPU 架构为 amd64
# go build -o /app/chat-server:将编译结果输出为 /app/chat-server 可执行文件
# ./server/main:指定入口源码包路径 也就是main包下的server.go文件
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/chat-server ./server/main
# 运行阶段
# 开始一个新的构建阶段,使用更小的 alpine:latest 作为基础镜像(不包含 Go 编译环境)
# 目的是只保留运行程序所需的最小环境,大幅减小最终镜像体积
FROM alpine:latest
# 安装 ca-certificates 包,提供 SSL/TLS 证书信任链
# --no-cache 表示不保留 apk 的缓存,避免增大镜像体积
# 这对需要发起 HTTPS 请求(如调用 API、访问数据库等)的程序是必需的
RUN apk --no-cache add ca-certificates
# 从前面定义的 builder 阶段中,复制编译好的 /app/chat-server 二进制文件到当前镜像的根目录(. 即 /)
# 这是多阶段构建的核心:只拷贝最终需要的产物,丢弃所有构建工具和中间文件
COPY --from=builder /app/chat-server .
# 声明容器在运行时会监听 15000 端口
# 注意:EXPOSE 只是文档化作用,实际端口映射需在 docker run -p 时指定
EXPOSE 15000
# 设置容器启动时默认执行的命令:运行 ./chat-server 程序
CMD ["./chat-server"]
docker-compose.yaml
# docker-compose.yml
version: '3.8'
services:
# ========== 聊天服务端 ==========
chat-server:
build: . # 使用当前目录的 DockerFile 构建
container_name: chat-server
restart: unless-stopped
ports:
- "15000:15000" # 暴露聊天端口给宿主机
environment:
# 数据库配置(容器间通过服务名通信)
- MYSQL_HOST=mysql
- MYSQL_PORT=3306
- MYSQL_USER=root
- MYSQL_PASSWORD=123456
- MYSQL_DATABASE=chat
# Redis 配置
- REDIS_ADDR=redis:6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- chat-net
# ========== MySQL ==========
mysql:
image: mysql:8.0
container_name: chat-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "123456"
MYSQL_DATABASE: chat
MYSQL_USER: root
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "127.0.0.1" ]
interval: 3s
timeout: 5s
retries: 10
volumes:
- mysql_data:/var/lib/mysql
command: --default-authentication-plugin=mysql_native_password
networks:
- chat-net
# ========== Redis ==========
redis:
image: redis:7-alpine
container_name: chat-redis
restart: unless-stopped
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 2s
timeout: 3s
retries: 5
volumes:
- redis_data:/data
networks:
- chat-net
# ========== 网络与持久化 ==========
networks:
chat-net:
driver: bridge
volumes:
mysql_data:
redis_data:
其中加入了健康检测机制, 也就是healthcheck, 相当于一个脚本, 用来保证数据库初始化完成才进入main函数程序入口
3. 修改相关连接配置
因为聊天室用到了mysql和redis, 所以初始化数据库的时候需要把连接地址更改
mysql
DB, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/chat")
if err != nil {
log.Fatal("数据库初始化失败:", err)
}
// 改为docker-compose文件中mysql的容器名也就是container_name, 我这里是chat-mysql
DB, err = sql.Open("mysql", "root:123456@tcp(chat-mysql:3306)/chat")
if err != nil {
log.Fatal("数据库初始化失败:", err)
}
redis
RDB = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
DB: 2,
})
// 同mysql改为容器名
RDB = redis.NewClient(&redis.Options{
Addr: "chat-redis:6379",
DB: 2,
})
客户端的连接地址改为虚拟机ip地址, 我这里是client.go
fmt.Println("客户端启动...")
conn, err := net.Dial("tcp", "127.0.0.1:15000")
if err != nil {
fmt.Println("连接服务器失败:", err)
return
}
// 改为
fmt.Println("客户端启动...")
conn, err := net.Dial("tcp", "192.168.xxx.xxx:15000") // 也就是你的虚拟机ip地址, 可以使用ifconfig命令查看ip地址
if err != nil {
fmt.Println("连接服务器失败:", err)
return
}
4. 上传项目到虚拟机
这里一般有两种常用方式:
1. 直接把目录拖拽到虚拟机目录中
2.使用scp命令
// username一般为root跟@虚拟机ip
scp -r windows下目录 username@192.168.56.101:虚拟机下目录
第二种方式前提是你的虚拟机已经开启ssh服务
示例: 比如我windows下这个路径
D:\baibaoplus\Go\go1.23.9\go code\go project\src
虚拟机ip为192.168.83.130
目标目录为/home/ryne/Documents/go
就可以在windows的CMD终端下输入:
scp -r "D:\baibaoplus\Go\go1.23.9\go code\go project\src\chat" ryne@192.168.83.130:/home/ryne/Documents/go/
如果执行成功之后就会看到go目录下有一个chat文件夹

注意:
这里查看你的windows和虚拟机路径是否一致, 如果不一致的话要修改go项目文件中路径参数
比如像我这样build时就会报error
=> ERROR [chat-server builder 5/5] RUN go mod tidy && CGO_ENABLED=0 GOOS 5.6s
5.450 server/main/server.go:5:2: package go_project/src/chat/server/service is not in std (/usr/local/go/src/go_project/src/chat/server/service) ------ failed to solve: process "/bin/sh -c go mod tidy && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/chat-server ./server/main" did not complete successfully: exit code: 1
这里就是路径参数不一致导致编译错误, import "go_project/src/chat/server/service", 只需要把go.mod里的module改成根目录即可, 这里是chat, 然后import把go_project/src部分删掉保证chat为根目录, 具体情况看你的go.mod文件根目录是什么, 进行具体修改即可
5. 构建并运行容器
1. docker compose build --no-cache
-
构建所有 Docker 镜像,确保使用最新的代码和依赖
-
--no-cache 忽略 Docker 缓存,从头开始构建每一层
-
适用于:代码更新后、依赖变更后、确保构建一致性
构建成功后会显示有三个镜像

2. docker compose up chat-server -d
- 启动chat-server服务端

这里看出chat-server是先等mysql和redis先启动再运行的
3. docker compose logs server -f
-
监控服务端的运行状态和日志输出

可以看出服务端正常启动没有异常
4. go run client.go 运行客户端代码
切换到客户端client文件夹的main函数运行

OK发现正常启动, 部署完成
如果发现这样的错误: go: github.com/go-sql-driver/mysql@v1.9.3: parsing go.mod: go.mod:3: invalid go version '1.21.0': must match format 1.23
升级一下虚拟机本地的go版本配置环境变量即可
更多推荐
所有评论(0)