I have a project with Golang backend, PostgreSQL DB and Redis.
I need to init PostgreSQL DB with this SQL:
ALTER USER postgres WITH password '123';
CREATE TABLE users
(
username VARCHAR (200) PRIMARY KEY,
pass VARCHAR (50)
);
My Dockerfile is:
# syntax=docker/dockerfile:1
FROM postgres:latest AS go2
ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD ''
ENV POSTGRES_DB postgres
EXPOSE 5432
ADD table.sql /docker-entrypoint-initdb.d/
FROM redis:latest AS go3
FROM golang:latest AS go
WORKDIR /app
COPY . .
RUN go build -o bin
ENTRYPOINT [ "/app/bin" ]
EXPOSE 8000
I build it with:
docker build . -t mycontainer
And run it with:
docker run -t mycontainer --network=bridge
But I get the following error:
[error] failed to initialize database, got error failed to connect to `host=172.17.0.2 user=postgres database=postgres`: dial error (dial tcp 172.17.0.2:5432: connect: connection refused)
My problems are:
I tried to run the container with this command:
docker run -t mycontainer --network=host
But the problems remain.
Update:
I created a docker-compose.yaml
file:
services:
db:
image: 'postgres:15.7'
restart: always
network_mode: bridge
volumes:
- './db/table.sql:/docker-entrypoint-initdb.d/init.sql'
ports:
- '5433:5432'
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 123
redis:
image: 'redis:7.0.12'
network_mode: bridge
ports:
- '6380:6379'
backend:
build: ./backend
network_mode: bridge
ports:
- '8000:8089'
depends_on:
- db
My PostgreSQL DB and Redis work good and I can connect to them.
But when I run my backend container it throws this error:
[error] failed to initialize database, got error failed to connect to `host=127.0.0.1 user=postgres database=postgres`: dial error (dial tcp 127.0.0.1:5433: connect: connection refused)
In your Dockerfile you combine PostgreSQL, Redis and your Go app.
But according to Docker's docs it's suggested to split them up:
It's best practice to separate areas of concern by using one service per container.
Thus, let's simplify your Dockerfile down to the Go app alone:
FROM golang
WORKDIR /app
COPY ./app .
RUN go build -o bin
ENTRYPOINT [ "/app/bin" ]
Fun fact, the EXPOSE
instruction used by you does not expose anything:
The
EXPOSE
doesn't publish the port. It functions as a type of documentation between the person who builds the image and the person who runs the container.
Since you are the person who runs the container, it's unnecessary. I'd use it only if I publish the Dockerfile to a registry so that others could download and use it. That's why I removed it.
Optional: The following slightly improved version should allow faster rebuilds:
FROM golang
WORKDIR /app
COPY go.mod go.sum ./ # ddd
RUN go mod download
COPY *.go ./
RUN go build -o bin
CMD [ "/app/bin" ]
Now let's talk about the error you experience.
Each service is in a separate container, but you are trying to connect from backend
to db
like if they are running in a single container. Think about them as running on separate machines. This way it should be obvious why you can't access db
on localhost of backend
.
You defined network_mode: bridge
for all containers. Thus they all will be attached to the default bridge network. It means they can access each other by their IPs. But it's tricky to get IPs. Thankfully, there is an easier way. Just define a custom bridge network:
networks:
my_network:
Here my_network
is a user defined network and it defaults to driver: bridge
. As opposed to the default bridge network, user defined bridge networks allow automatic service discovery:
On user-defined networks containers can not only communicate by IPs, but can also resolve a container name to an IP. This is called automatic service discovery.
It means, in the code of your backend
service you will be able to use db:5432
to access your PostgreSQL DB. The db
here will be automatically replaced by IP of the db
service.
But to access your services from the host use ports
attribute to map host's port to container's port. Your backend
service maps 8000:8089
. It means, the backend
service is accessible on the host as localhost:8000
, but from other services as backend:8089
.
This is a complete minimal example of compose file you need:
networks:
my_network:
services:
db:
image: postgres
volumes:
- ./db/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
networks:
- my_network
ports:
- 5433:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 123
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 1s
retries: 100
redis:
image: redis
networks:
- my_network
ports:
- 6380:6379
backend:
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
build: ./backend
networks:
- my_network
ports:
- 8000:8089
Here, the ./postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
maps data from ./postgres/docker-entrypoint-initdb.d
on the host to docker-entrypoint-initdb.d
within the db
container. Any SQL there will init PostgreSQL DB.
The backend.depends_on
together with db.healthcheck
allow to make sure the backend
starts when db
is healthy (i.e. PostgreSQL DB is ready to accept connections).
Finally, a complete working example is here.