Search code examples
dockergodocker-composecockroachdb

How to connect to CockroachDB with docker-compose?


I have a docker-compose file in which I locally deploy the database and the application on go

services:
      node_1:
          container_name: node_1
          image: cockroachdb/cockroach:latest
          command: start --insecure
          ports:
              - "26258:26258"
              - "8081:8081"
          networks:
            - network_cockroachdb 
      node_2:
          container_name: node_2
          image: cockroachdb/cockroach:latest
          hostname: node_2
          ports:
            - "26257:26257"
            - "8080:8080"
          command: start --insecure --join=node_1
          networks:
            - network_cockroachdb 
          network_mode: 'host'
      app:
          build: .
          ports:
            - "12121:12121"
          environment:
            app_port: '12121'
            db_host: "node_2"
            db_port: 26257
            db_user: root
            db_password: 123
            db_database: mydb
          depends_on:
            - node_2
          links:
            - node_2
          networks:
            - network_cockroachdb 
    networks:
        network_cockroachdb:
            driver: bridge 

Go file:

func main() {  
    port, _ := strconv.Atoi(os.Getenv("db_port"))

    dbConfig := storage.ConnectionConfig{
        Host:     os.Getenv("db_host"),
        Port:     port,
        User:     os.Getenv("db_user"),
        Password: os.Getenv("db_password"),
        DBName:   os.Getenv("db_database"),
        SSLMode:  "verify-full",
    }

    log.Println("url: ", dbConfig.String())

    db, err := storage.NewCockroachDB(context.Background(), dbConfig)

    if err != nil {
        log.Fatal(err)
    }
}

in which the connection to the database is made. But the connection fails, and the wrong port is forwarded: instead of 26257, 26258 is forwarded. How to fix this?


Solution

    1. Don't use links; this feature has been deprecated for years now and is only maintained for backwards compatibility. Docker maintains DNS for containers so you can simply use the service name as a hostname when establishing connections.

    2. You cannot combine port forwarding with network_mode: host.

    3. Your use of depends_on is effectively a no-op; there's a good chance your application is trying to connect to the database before the database is ready to handle connections.

      In fact, your database cluster will not accept connections until you run cockroach init, so you are definitely hitting this issue.

    4. Your compose file will fail to start node_1 with the following error:

      * ERROR: ERROR: no --join flags provided to 'cockroach start'
      * HINT: Consider using 'cockroach init' or 'cockroach start-single-node' instead
      *
      ERROR: no --join flags provided to 'cockroach start'
      HINT: Consider using 'cockroach init' or 'cockroach start-single-node' instead
      Failed running "start"
      
    5. Your port forwarding for node_1 is incorrect; there is nothing in the container listening on port 8081. You would want something like:

      ports:
        - 8081:8080
      

    Lastly, you don't indicate where the storage module in your sample code comes from, so I wasn't able to use it for testing things. I wrote this test program instead, which includes a loop to wait for the database to accept connections:

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "os"
        "time"
    
        pgx "github.com/jackc/pgx/v4"
    )
    
    func main() {
        connectionString := os.Getenv("db_uri")
    
        if connectionString == "" {
            connectionString = fmt.Sprintf("postgresql://%s@%s:%s/%s?sslmode=disable",
                os.Getenv("db_user"),
                os.Getenv("db_host"),
                os.Getenv("db_port"),
                os.Getenv("db_database"),
            )
        }
    
        var conn *pgx.Conn
        var err error
    
        for {
            conn, err = pgx.Connect(context.Background(), connectionString)
            if err == nil {
                break
            }
    
            log.Printf("connection failed (%v); will retry...", err)
            time.Sleep(1 * time.Second)
        }
        log.Printf("connected to database")
    
        var value int
        if err := conn.QueryRow(context.Background(), "select 1").Scan(&value); err != nil {
            panic(err)
        }
    
        fmt.Printf("All done.\n")
    }
    

    If we fix all of the above problems and generally clean up the compose file, we end up with:

    services:
      node_1:
        image: cockroachdb/cockroach:latest
        ports:
          - "8080:8080"
        command:
          - start
          - --insecure
          - --join=node_1,node_2
    
      node_2:
        image: cockroachdb/cockroach:latest
        ports:
          - "8081:8080"
        command:
          - start
          - --insecure
          - --join=node_1,node_2
    
      app:
        build: .
        environment:
          db_host: "node_2"
          db_port: 26257
          db_user: root
          db_password: 123
          db_database: mydb
    

    Note that this configuration intentionally does not publish the database ports on the host, since this isn't required in order for the application to access the database.

    When we docker compose up this configuration, we'll see the following from the database services:

    * INFO: initial startup completed.
    * Node will now attempt to join a running cluster, or wait for `cockroach init`.
    * Client connections will be accepted after this completes successfully.
    * Check the log file(s) for progress.
    

    And the following from the example application (which we expect):

    2023/09/01 12:53:20 connection failed (failed to connect to `host=node_2 user=root database=mydb`: dial error (dial tcp 10.89.1.46:26257: connect: connection refused)); will retry...
    

    We need to initialize the database:

    docker compose exec node_1 ./cockroach init --insecure --host=node_1
    

    After which we see the following from the database services:

    CockroachDB node starting at 2023-09-01 12:54:38.494304014 +0000 UTC m=+77.639236046 (took 77.4s)
    [...]
    

    And the sample application is able to connect and perform a query:

    2023/09/01 12:54:38 connected to database
    All done.
    

    The web UI for these nodes will be exposed on host ports 8080 and 8081.


    Lastly, you will probably want to create volumes to persist the database data. See e.g. this documentation for where to mount the volumes.