You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
}
解决方案

问题根源

  1. depends_on仅保证容器启动顺序:它不会等待MySQL服务完全初始化就绪,应用容器启动时MySQL可能还在加载数据或完成配置,导致连接失败。
  2. 环境变量冲突:docker-compose给app容器传递了环境变量,但镜像构建时又复制了.env.example到容器内,godotenv.Load()会优先读取容器内的.env文件,可能覆盖外部传递的配置,或者因文件加载逻辑报错影响连接。
  3. 无连接重试机制:应用启动后直接尝试连接,一旦失败就终止,没有应对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:13:15