准备工作

1、确定项目目录结构

理想情况下在虚拟机中的目录应该有类似下图一样的结构:

  • yml文件与客户端,服务端同级

  • 服务端运行文件(server.go)与服务端 Dockerfile 同级,客户端也是如此

2、上传项目

1、最简单直观、基于图形化界面

直接将文件从主机拖进虚拟机

2、最通用、基于网络传输

使用 SCP/SFTP 命令

# 递归上传整个目录, 注意要上传的目录必须已经存在!
scp -r 要上传的项目目录 虚拟机用户名@虚拟机ip地址:要上传到的目录
# 示例,对应我上面的目录结构
eg : scp -r D:\Goproject\onlineChatRoom root@ip地址:/go-project

这里项目目录如果特别大的话,可以先在主机使用命令 tar / zip 进行压缩,然后再进行 scp 传输,到虚拟机上再解压

完成之登录虚拟机、进入 /go-project 然后使用 tree 命令、会得到跟我类似的项目目录、那么恭喜你,完成了第一步!

3、客户端Dockerfile

# 构建阶段
FROM golang:1.24 AS builder 
# 基于官方 Golang 1.24 版本的镜像
# 给这个构建阶段命名为 "builder",便于后续引用
WORKDIR /app
# 在容器内设置工作目录为 /app,构建阶段后续命令都在此目录下执行
COPY . .
# 将宿主机根目录所有文件复制到容器的 /app 目录
# 第一个 . 表示构建上下文的所有内容,也就是项目下的所有文件
# 第二个 . 表示容器的 /app 目录
Run go env -w GOPROXY=https://goproxy.cn,direct
#设置 Go 模块代理为国内镜像源,加速依赖下载
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/client ./client
# 这一步的作用就是:1、整理模块依赖 2、将app/下的client包下的所有go文件编译成可执行的二进制文件存放在 /app/client
# 整理 Go 模块依赖,添加缺失的模块,移除未使用的模块
# CGO_ENABLED=0: 禁用 CGO,生成纯静态二进制文件,不依赖系统库
# GOOS=linux: 指定目标操作系统为 Linux
# GOARCH=amd64: 指定目标架构为 x86-64
# go build -o /app/client ./client
# -o /app/client: 指定输出文件路径和名称
# ./client: 要编译的包路径(通常是包含 main.go 的目录)
​
​
# 运行阶段
FROM alpine:latest
# 基于 Alpine Linux 镜像,这是一个极简的 Linux 发行版
# 相比使用完整的 Golang 镜像,大幅减小最终镜像体积
WORKDIR /app
# 设置运行阶段的工作目录为 /app
COPY --from=builder /app/client .
#--from=builder: 从之前的构建阶段(builder)复制文件
#/app/client: 源文件路径(构建阶段生成的二进制文件)
#. : 目标路径(当前工作目录 /app)
CMD ["./client"]
#容器启动时执行的命令,执行当前目录下的 client 二进制文件

4、服务端Dockerfile

# 跟客户端Dockerfile 基本一样,不再注释
# 构建阶段
FROM golang:1.24 AS builder
​
WORKDIR /app
COPY . .
​
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/server ./server
​
# 运行阶段
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/server .
EXPOSE 8080
# 声明容器运行时监听的端口 8080
CMD ["./server"]
​

5、docker-compose.yml

services:
  mysql: # 服务名
    image: mysql:8.0
    container_name: mysql # 容器名
    restart: always # 容器异常退出时自动重启
    environment: # 环境
      MYSQL_ROOT_PASSWORD: "12345"  # root 用户密码
      MYSQL_DATABASE: database      # 创建默认数据库
      MYSQL_USER: user              # 创建普通用户
      MYSQL_PASSWORD: password      # 普通用户密码
    ports:
      - "3306:3306"                 # 主机3306端口映射到容器3306端口
    volumes:
      - mysql_data:/var/lib/mysql   # 挂载、数据持久化
    networks:
      - go-net                      # 加入自定义网络
​
  redis:
    image: redis:5.0
    container_name: redis
    restart: always
    ports:
      - "6379:6379"                  # Redis 默认端口
    volumes:
      - redis_data:/data
    command: ["redis-server", "--appendonly", "yes"]    # 启用 AOF 持久化、确保数据不丢失
    networks:
      - go-net
​
  server:
    build:
      context: .                    # 构建上下文为当前目录
      dockerfile: server/Dockerfile # 使用 server/Dockerfile 构建
    container_name: go-server       # 容器名
    depends_on:                     # 依赖
      - mysql
      - redis
    environment:
      MYSQL_HOST: mysql             # 使用服务名而非IP
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DB: database
      REDIS_ADDR: redis:6379  
    networks:
      - go-net
    ports:
      - "8080:8080"                 # 暴露服务端口
​
  client:
    build:
      context: .
      dockerfile: client/Dockerfile
    container_name: go-client
    depends_on:                     # 依赖
       - server
    environment:
      SERVER_ADDR: go-server:8080   # 使用容器名和端口
    networks:
      - go-net
    command: tail -f /dev/null      # 保持容器运行,覆盖掉客户端Dockerfile的CMD["./client"]
                                    # 这样是确保容器保持运行,而客户端是手动启动
​
networks:
  go-net:
    driver: bridge
​
volumes:
  mysql_data:
  redis_data:

好了,恭喜你完成了准备工作!

小问题

1、mysql 连接

dsn := "root:5812@tcp(localhost:3306)/onlinechatroom?charset=utf8mb4&parseTime=True&loc=Local"
// 这是在主机上的连接mysql 需要改为:
//root:密码@tcp(容器名:端口)
dsn := "root:12345@tcp(mysql:3306)/onlinechatroom?charset=utf8mb4&parseTime=True&loc=Local"

2、 redis 连接

RDB = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
        PoolSize: 100, //连接池的大小
    })
// 改为:
RDB = redis.NewClient(&redis.Options{
        Addr:     "redis:6379", //容器名:端口
        Password: "",
        DB:       0,
        PoolSize: 100, //连接池的大小
    })

3、 客户端连接服务端

conn, err := net.Dial("tcp", "localhost:8080") //连接服务端
// 改为
conn, err := net.Dial("tcp", "go-server:8080") //容器名:端口

运行

1、docker compose build --no-cache

  • 构建所有 Docker 镜像,确保使用最新的代码和依赖

  • --no-cache 忽略 Docker 缓存,从头开始构建每一层

  • 适用于:代码更新后、依赖变更后、确保构建一致性

运行完毕之后会成功构建两个镜像:

2、docker compose up -d mysql reids

  • 后台启动 mysql 和 redis服务

运行完毕之后会拉取 mysql 和 redis 镜像

然后根据这两个镜像运行容器 mysql,redis 、容器名在yml文件中已经决定

因为我这里已经全部运行了,正常到这一步是只有 mysql 和 redis 的

3、初始化数据库

  • 因为我的项目是要用到 mysql 中 onlineChatRoom 库中的 user 表的

  • docker exec -it 容器名 mysql -uroot -p 、进入 mysql

  • create database onlineChatRoom 、创建需要用到的库

  • user onlineChatRoom 、使用库

  • create table user ··········· 、创建表

4、docker compose up -d server

  • 后台启动服务端

  • 此时 mysql 和 redis已经运行,server 可以正常连接它们

  • server 服务会读取环境变量连接到 mysql 和 redis

  • 可以观察服务端启动日志,确认数据库连接成功

5、docker compose up -d client

  • 最后后台启动客户端

  • client 依赖 server 服务,等 server 就绪后再启动

  • client 容器会保持运行(因为 tail -f /dev/null

第四步和第五步运行完毕之后就会如下所示:

6、docker compose logs server -f

  • 监控服务端的运行状态和日志输出

  • logs:查看容器日志

  • server:指定查看 server 服务的日志

  • -f:跟随模式(follow),实时显示新日志

  • 用于:调试应用、监控运行状态、查看错误信息

运行完毕之后能够监听服务端的日志:

7、docker exec -it go-client /bin/sh

  • 进入客户端容器的交互式 shell

  • exec:在运行中的容器内执行命令

  • -it:交互式终端(interactive + TTY)

  • go-client:容器名称(在 docker-compose.yml 中定义的 container_name)

  • /bin/sh:要执行的 shell(Alpine Linux 默认使用 sh)

运行完毕之后会进入到 /app # 、app也是在上面自己起的名字

./client 表示运行当前目录下的可执行文件client、其实就是运行客户端、然后就可以进行操作了

更多推荐