Docker Compose启动Go应用时数据库初始化失败:连接被拒绝
问题描述
执行docker compose up -d时触发错误:
Failed to initialize database, got error dial tcp 192.168.16.2:3306: connect: connection refused
多次执行docker compose up --build --remove-orphans --force-recreate -d并清理容器后问题仍存在。本地可通过mysql -u lmhuong711 -p(密码123)正常连接数据库,但应用容器始终无法建立连接。相关配置文件如下:
相关配置文件
docker-compose.yml
version: "3" services: app: container_name: app build: context: . dockerfile: api.Dockerfile restart: always ports: - 8071:8080 environment: - GIN_MODE=$GIN_MODE - APP_PORT=$APP_PORT - DB_HOST=$DB_HOST - DB_PORT=$DB_PORT - DB_USER=$DB_USER - DB_PASS=$DB_PASS - DB_NAME=$DB_NAME depends_on: - mysql mysql: container_name: mysql image: mysql:8.0 ports: - 3366:3306 environment: MYSQL_USER: $DB_USER MYSQL_PASSWORD: $DB_PASS MYSQL_ROOT_PASSWORD: $DB_PASS MYSQL_DATABASE: $DB_NAME volumes: - db_data:/var/lib/mysql volumes: db_data:
api.Dockerfile
FROM golang:1.22.3-alpine as builder LABEL maintainer="lmhuong <lmhuong@gmail.com>" # Set the Current Working Directory inside the container WORKDIR /app # Copy go mod and sum files COPY go.mod go.sum ./ # Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed RUN go mod download # Copy the source from the current directory to the Working Directory inside the container COPY . . # Build the Go app RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . ######## Start a new stage from scratch ####### FROM alpine:3.19 RUN apk --no-cache add ca-certificates WORKDIR /root/ # Copy the Pre-built binary file from the previous stage COPY --from=builder /app/main . # copy env file to root directory COPY --from=builder /app/.env.example .env # Command to run the executable CMD ["./main"]
.env.example
GIN_MODE=release # debug, release APP_PORT=8080 DB_HOST=mysql DB_PORT=3306 DB_USER=lmhuong711 DB_PASS=123 DB_NAME=my_db DB_CHARSET=utf8mb4 DB_LOC=Local
db.go
package db import ( "database/sql" "fmt" "lmhuong711/vdt24-be/models" "log" "os" // "github.com/kristijorgji/goseeder" "github.com/lpernett/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" ) var Db *gorm.DB func InitDb() *gorm.DB { // goseeder.WithSeeder(connectToDbOrDie, func() { // }) connectToDbOrDie() return Db } func connectToDbOrDie() *sql.DB { var err error err = godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } user := os.Getenv("DB_USER") password := os.Getenv("DB_PASS") host := os.Getenv("DB_HOST") port := os.Getenv("DB_PORT") database := os.Getenv("DB_NAME") dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, port, database) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { fmt.Printf("Error connecting to database : error=%v", err) return nil } db.AutoMigrate(&models.Students{}) db.AutoMigrate(&models.Domains{}) db.AutoMigrate(&models.MapStudentDomain{}) Db = db ret, err := db.DB() if err != nil { fmt.Printf("Error getting database connection : error=%v", err) return nil } return ret }
解决方案
问题根源
depends_on仅保证容器启动顺序:它不会等待MySQL服务完全初始化就绪,应用容器启动时MySQL可能还在加载数据或完成配置,导致连接失败。- 环境变量冲突:docker-compose给app容器传递了环境变量,但镜像构建时又复制了
.env.example到容器内,godotenv.Load()会优先读取容器内的.env文件,可能覆盖外部传递的配置,或者因文件加载逻辑报错影响连接。 - 无连接重试机制:应用启动后直接尝试连接,一旦失败就终止,没有应对MySQL启动延迟的重试逻辑。
修复步骤
1. 解决MySQL服务就绪等待问题
方式一:通过容器启动命令添加等待逻辑
修改docker-compose.yml中app服务的command:
services: app: # 保留原有配置 command: sh -c "while ! nc -z mysql 3306; do sleep 1; echo 'Waiting for MySQL...'; done; ./main"
同时在api.Dockerfile的alpine阶段安装netcat:
RUN apk --no-cache add ca-certificates netcat-openbsd
方式二:在Go代码中添加连接重试
修改connectToDbOrDie函数,增加重试逻辑:
// 记得导入time包 import "time" func connectToDbOrDie() *sql.DB { var err error err = godotenv.Load() if err != nil { log.Println("No .env file found, using environment variables") } user := os.Getenv("DB_USER") password := os.Getenv("DB_PASS") host := os.Getenv("DB_HOST") port := os.Getenv("DB_PORT") database := os.Getenv("DB_NAME") dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, port, database) var db *gorm.DB // 重试5次,每次间隔2秒 for i := 0; i < 5; i++ { db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err == nil { break } fmt.Printf("Failed to connect, retrying in 2s... error=%v\n", err) time.Sleep(2 * time.Second) } if err != nil { fmt.Printf("Final connection failed: error=%v", err) log.Fatal(err) } db.AutoMigrate(&models.Students{}) db.AutoMigrate(&models.Domains{}) db.AutoMigrate(&models.MapStudentDomain{}) Db = db ret, err := db.DB() if err != nil { fmt.Printf("Error getting database connection : error=%v", err) log.Fatal(err) } return ret }
2. 统一环境变量管理
二选一即可:
- 保留docker-compose传递环境变量:删除api.Dockerfile中
COPY --from=builder /app/.env.example .env这一行,同时修改godotenv.Load()的错误处理,避免因找不到.env文件直接终止。 - 使用容器内的
.env文件:删除docker-compose.yml中app服务的environment配置块,确保镜像内的.env.example配置正确。
3. 验证MySQL容器状态
启动后进入mysql容器确认服务正常:
docker exec -it mysql mysql -u lmhuong711 -p123 -e "SELECT 1;"
如果能返回1,说明MySQL服务正常,问题集中在应用连接逻辑上。
内容的提问来源于stack exchange,提问作者Huong Minh
相关产品推荐
相关产品推荐

