Could not connect to postgres server in a docker f

2020-02-15 02:10发布

问题:

I would like to run a dockerized Django app with a dockerized postgres.

I run the dockerized Django app by using:

docker run --rm --env-file /path/to/variables -d -p 8000:8000 django_app:test

I run a dockerized postgres by using:

docker run --rm -d --env-file /path/to/secrets/variables -p 5432:5432 \
    -v "$PWD/my-postgres.conf":/etc/postgresql/postgresql.conf \
    --mount src=/path/to/db/data,dst=/var/lib/postgresql/data,type=bind \
    postgres:alpine -c 'config_file=/etc/postgresql/postgresql.conf'

my postgres config is the default config that is suggested in the postgres docker container documentation. It is essentially a config file that contains listen_addresses = '*'

I use the same environment variables for both containers:

DJANGO_SETTINGS_MODULE=settings.module
PROJECT_KEY=xxyyzzabcdefg
DB_ENGINE=django.db.backends.postgresql
POSTGRES_DB=db_name
POSTGRES_USER=db_user
POSTGRES_PASSWORD=verydifficultpassword
POSTGRES_HOST=localhost # I've also tried to use 0.0.0.0
POSTGRES_PORT=5432

My Django settings module for the database is:

 DATABASES = {
    'default': {
        'ENGINE': os.environ.get('DB_ENGINE'),
        'NAME': os.environ.get('POSTGRES_DB'),
        'USER': os.environ.get('POSTGRES_USER'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
        'HOST': os.environ.get('POSTGRES_HOST'),
        'PORT': os.environ.get('POSTGRES_PORT')
        }
  }

However, I keep on getting:

django.db.utils.OperationalError: could not connect to server: Connection refused
    Is the server running on host "0.0.0.0" and accepting
    TCP/IP connections on port 5432?

The Dockerfiles for my django app looks like:

FROM python:alpine3.7
COPY --from=installer /app /app
# required for postgres
COPY --from=installer /usr/lib /usr/lib
COPY --from=installer /usr/local/bin /usr/local/bin
COPY --from=installer /usr/local/lib /usr/local/lib
ARG SETTINGS_MODULE
WORKDIR /app
ENTRYPOINT python manage.py migrate &&\
           python manage.py test &&\
           python manage.py create_default_groups &&\
           python manage.py set_screen_permissions &&\
           python manage.py create_test_users &&\
           python manage.py init_core &&\
           python manage.py runserver 0.0.0.0:8000

Another interesting fact is that if I run the app locally python manage.py runserver and have the postgres container running, the app seems to be working.

Can anyone help me try to figure out why am I getting a connection refused? Thanks in advance!

回答1:

Just make use of user-defined bridge networking. First, leverage your knowledge with reading a short explanation of different types of networks in Docker: https://docs.docker.com/network/bridge/

Second, define your own network

docker network create foo

Next, run your containers connected to this network:

docker run --rm --env-file /path/to/variables -d --network foo django_app:test
docker run --rm -d ... --network foo postgres:alpine ...

Notice in both commands --network foo. Also you dont need to expose ports in this case - inside user-defined networks it is done automatically:

Containers connected to the same user-defined bridge network automatically expose all ports to each other, and no ports to the outside world. This allows containerized applications to communicate with each other easily, without accidentally opening access to the outside world.

Third, give your containers human readable host names with --name bar

docker run ... --network foo --name my-django django_app:test ...
docker run ... --network foo --name my-postgres postgres:alpine ...

And finally fix the connection string - change from localhost to container name, like my-postgres:

...
POSTGRES_HOST=my-postgres
...


回答2:

In your docker container with Django there is no Postgres running on localhost. you need to point to the Postgres docker container by specifying the container name instead of localhost Also, the Postgres container and your app have to be in the same network. see https://docs.docker.com/network/

To create a network run:

docker network create --driver bridge my-network

To run docker container in the network use:

docker run --network my-network -it container_name

To setup Postgres in container with Django:

POSTGRES_HOST=postgres-container-name


回答3:

In your case you are not specifying the container names. When setting POSTGRES_HOST=localhost you're telling the django container to connect to itself. Which is naturally going to fail as there is no postgres server present in the django container.

Your two containers are using the default bridge network (docker0). In a bridge network the container names are used as host names.

This means means that from your django container you can access the postgres container by using its name as the hostname.

To be able to connect to postgres from you django app you need to:

  1. Give names to your containers by using the --name option. Example: docker run --rm --name mypostgresdb -d ... postgres:alpine -c ...
  2. First create the postgres container and wait for it to boot. Then create the database and user/password.
  3. Only when postgres is up, create the django container. If the postgres container is not up your django container will fail since it's trying to run migrations in the entrypoint.
  4. The POSTGRES_HOST env variable in your django container should receive the postgres container's name as a value. Example: POSTGRES_HOST=mypostgresdb

To check the connection to you postgres container you can ping it from your django container.