3
func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        log.Println("database connection error", err)
        return nil, nil, err
    }

    err = client.Ping(context.TODO(), readpref.Primary())
    if err != nil {
        log.Println("err", err)
        return
    }
    log.Println("Successfully connected and pinged.")

    dbName := GetDatabaseName()
    database = client.Database(dbName)

    log.Println(dbName, database.Name())
    return
}

This golang app is running on one container and mongodb on other. Above is my function for checking the database connection. After reading some suggestions on internet I am trying to use container name instead of localhost. Please provide your inputs on Dockerfile or docker-compose file.

Dockerfile

FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0 
RUN go build -o main .

FROM alpine:latest
COPY --from=builder /app ./

EXPOSE 8080
ENTRYPOINT ["./main"]

docker-compose

version: '3.7'
services:
  db:
    image: mongo
    restart: always
    platform: linux/x86_64
    networks:
      - default
    ports:
      - "27017:27017"
    container_name: mongodb

  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - db

on running these two containers, app container will give db connection error.

As13
  • 59
  • 6
  • What is the error precisely saying? Can you show the error message? You don't use any credentials to connect, this might be the problem. From your question, it is not clear if it's an actual networking issue, but at first glance it's not. – The Fool Apr 16 '22 at 13:21
  • @TheFool hello, "client is disconnected" error shows up when the code tries to Ping the db. – As13 Apr 16 '22 at 18:07
  • see the posted answer. Its correct. You never call connect. – The Fool Apr 16 '22 at 18:09
  • 1
    There's also a potential problem if the application starts up before the database is fully initialized; `depends_on:` isn't enough here, and you can get a "connect refused" type error in this case. See for example [Docker Compose wait for container X before starting Y](https://stackoverflow.com/questions/31746182/docker-compose-wait-for-container-x-before-starting-y). – David Maze Apr 16 '22 at 20:50
  • @DavidMaze Thanks for the comment. Will go through it. – As13 Apr 17 '22 at 02:55

2 Answers2

1

You have the correct service name. As been said here, you can use both db or mongodb as the host name. (You don't need to set a user and pass...)

But you have a bug in the code. You only initialize a new client, you don't connect to it. You can use Connect instead of NewClient, or do client.Connect. Also, you are using context.Todo instead of the ctx with the timeout (Not a biggy, but still).

You should do this:

func GetDatabase() (database *mongo.Database, ctx context.Context, err error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        log.Println("database connection error", err)
        return nil, nil, err
    }

    err = client.Ping(ctx, readpref.Primary())
    if err != nil {
        log.Println("err", err)
        return
    }
    log.Println("Successfully connected and pinged.")

    dbName := GetDatabaseName()
    database = client.Database(dbName)

    log.Println(dbName, database.Name())
    return
}
oren
  • 3,159
  • 18
  • 33
  • 1
    thanks for the answer. I tried it. When code tried to ping the db, it gives this error. ```server selection error: server selection timeout, current topology: { Type: Unknown, Servers: [{ Addr: mongodb:27017, Type: Unknown, Last error: connection() error occured during connection handshake: dial tcp: lookup mongodb on 192.168.65.5:53: read udp 172.17.0.2:47886->192.168.65.5:53: i/o timeout }, ] } ``` – As13 Apr 16 '22 at 18:21
0

As pointed out in the comment and the answer by oren. You have 2 issues.

  1. You never call connect. Here is some example code, more or less copy pasted from the driver readme: https://github.com/mongodb/mongo-go-driver
func main() {

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        panic(err)
    }

    defer func() {
        if err = client.Disconnect(ctx); err != nil {
            panic(err)
        }
    }()

    ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    err = client.Ping(ctx, readpref.Primary())
    if err != nil {
        panic(err)
    }

    fmt.Println("Connected to MongoDB!")

}
  1. The db might not be ready by the time you try to connect. You could ensure that the db is ready bore the app starts by using a healthcheck and a condition in depends_on.
name: example

services:
  backend:
    image: backend
    build: ./
    depends_on:
      mongodb:
        condition: service_healthy

  mongodb:
    image: mongo
    healthcheck:
      test: mongo --norc --quiet --host=localhost:27017 --eval "db.getMongo()"
      interval: 30s
      timeout: 2s
      retries: 3
      start_period: 15s

You will see that the app reports that it has connected and the shuts down.

example-backend-1  | Connected to MongoDB!
example-backend-1 exited with code 0

There might be a better way to do health checks on the db, I am no mongodb expert, so you have to research if there is something.


Another thought is, that it may be better to build some kind of retry logic and health checks into your application. So that it retries to connect to the db and reports if there is currently no connection.

Depending on the kind of service you are building, this may or may not make sense. If you have a simple job that needs to run a one time task, it would make sense to wait for the db. If you have something like a rest API, health checks could make more sense.

For example:

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://mongodb:27017"))
    if err != nil {
        panic(err)
    }

    defer func() {
        if err = client.Disconnect(ctx); err != nil {
            panic(err)
        }
    }()

    http.HandleFunc("/health/ready", func(w http.ResponseWriter, r *http.Request) {
        ctx, cancel = context.WithTimeout(r.Context(), 2*time.Second)
        defer cancel()
        if err = client.Ping(ctx, readpref.Primary()); err != nil {
            log.Printf("ping error: %v", err)
            w.WriteHeader(http.StatusServiceUnavailable)
            return
        }
        w.WriteHeader(http.StatusNoContent)
    })

    http.ListenAndServe(":8080", nil)

}
The Fool
  • 16,715
  • 5
  • 52
  • 86