Search code examples
postgresqldockergodocker-compose

Docker-compose with Golang and Postgres connection is refused


I am trying to make the connection with PostgreSQL using Docker Compose. I have a Golang CLI application called flow and it creates, views, adjust's, gets, and removes the budget information.

When I write the docker docker-compose up -d, it creates two images and one container.

Images are

  1. flow-budget_planner
  2. postgres

Container is

  1. PostgresContainer

Command execution

  • When I run this command, docker exec -it <Container-ID> psql -U posgresql, it put in the PostgreSQL terminal to handle the database.

  • When I run the postgres, docker run -it -e POSTGRES_PASSWORD=password postgres, it gives me the message that database system is ready to accept connections.

  • When I run the flow-budget_planner in another tab while the Postgres image is running, I get the connection refused message. Here is the command: docker run -it flow-budget_planner ./flow budget create -c category -a 300

What should I do to make the connection established and enter the data in PostgreSQL successfully using Docker? Please tell me if you need more info.

Here is the folder structure of it:

├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── cmd
│   ├── budget
│   │   ├── budget.go
│   │   └── handler
│   │       ├── adjust.go
│   │       ├── create.go
│   │       ├── get.go
│   │       ├── remove.go
│   │       └── view.go
│   └── other services
│       └── service.go
├── db
│   └── budget_db
│       ├── connection.go
│       └── migrations
│           └── 001_create_budget_table.sql
├── docker-compose.yml
├── go.mod
├── go.sum
└── internal
    ├── app
    │   └── budget
    │       └── budget.go
    └── middleware
        └── env.go

Here is the PostgreSQL connection code

type Variables struct {
    Host     string
    Port     string
    User     string
    Password string
    DBName   string
    SSLMode  string
}

func Connection() (*sql.DB, error) {
    v := Variables{
        Host:     middleware.LoadEnvVariable("host"),
        Port:     middleware.LoadEnvVariable("port"),
        User:     middleware.LoadEnvVariable("user"),
        Password: middleware.LoadEnvVariable("password"),
        DBName:   middleware.LoadEnvVariable("dbname"),
        SSLMode:  middleware.LoadEnvVariable("sslmode"),
    }

    connectionString := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", v.Host, v.Port, v.User, v.Password, v.DBName, v.SSLMode)
    db, err := sql.Open("postgres", connectionString)
    if err != nil {
        return nil, err
    }

    err = db.Ping()
    if err != nil {
        return nil, err
    }
    return db, nil
}

Here is the Dockerfile

FROM golang:1.22.0-alpine3.19

RUN mkdir -p /app

WORKDIR /app

COPY go.mod /app
COPY go.sum /app

COPY . /app

RUN go mod download

RUN go build -o flow .

CMD ["./flow", "budget"]

Here is the Docker Compose file

version: "3.8"
services:
  postgres:
    image: postgres:latest
    hostname: localhost
    container_name: PostgresContainer
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: flow
    ports:
      - '5432:5432'
    volumes:
      - postgres_data:/var/lib/postgresql/data
    

  budget_planner:
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - postgres
    environment:
      DATABASE_URL: postgresql://postgres:password@localhost:5432/flow


volumes:
  postgres_data:
    driver: local

Solution

  • 1st problem

    Your code seems to refer to these env variables:

        v := Variables{
            Host:     middleware.LoadEnvVariable("host"),
            Port:     middleware.LoadEnvVariable("port"),
            User:     middleware.LoadEnvVariable("user"),
            Password: middleware.LoadEnvVariable("password"),
            DBName:   middleware.LoadEnvVariable("dbname"),
            SSLMode:  middleware.LoadEnvVariable("sslmode"),
        }
    

    But the only env variable you provide to the application is the connection string DATABASE_URL:

        environment:
          DATABASE_URL: postgresql://postgres:password@localhost:5432/flow
    

    Shouldn't you be using that?

    Like this:

    sql.Open("postgres", middleware.LoadEnvVariable("DATABASE_URL"))
    

    Or if that does not work, try manually adding the connection string first:

    sql.Open("postgres", "postgres://postgres:password@postgres:5432/flow")
    

    2nd problem

    The connection string itself is wrong: postgresql://postgres:password@localhost:5432/flow. localhost when used in docker container refers to the localhost of the container itself, not to the localhost of the host machine. Containers started with docker-compose can refer to each other with the service name as the hostname by default.

    The driver name might need to be be postgres too, not postgresql, though it might be interchangeable.

    This url should work anyway:

    postgres://postgres:password@postgres:5432/flow
    

    Edit: Both postgres and postgresql work, according to Postgres documentation.