0

Ok, so, I'm trying to start a project using Django and docker. The first step is simple, I just want to launch a default django app in container and see the django starting screen in my browser. I also want to hook up a simplest postgres container to run along with it. Everything great. I created a simple image for my app, nothing fancy:

FROM python:3.7
RUN mkdir /app
COPY requirements.txt /app/
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /code/

And a docker-compose.yml:

version: '3'

services:
  python:
    build:
      context: .
      dockerfile: docker/python/Dockerfile
    volumes:
      - .:/app
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - "8000:8000"
    depends_on:
      - db
    restart: always
  db:
    image: postgres:9.5
    environment:
      POSTGRES_USER: cards
      POSTGRES_DB: cards
      POSTGRES_PASSWORD: cards

As you can see, I'm not even copying any source code into the docker image, I'm mounting a volume for django to read it from host.

The problem is when I start the containers, the django server is starting too soon for the postgres to be ready, and psycopg2 throws an OperationalError. Here are the logs, when I start fresh containers:

Creating network "backend_default" with the default driver
Creating backend_db_1 ... done
Creating python       ... done
Attaching to backend_db_1, python
db_1      | The files belonging to this database system will be owned by user "postgres".
db_1      | This user must also own the server process.
db_1      |
db_1      | The database cluster will be initialized with locale "en_US.utf8".
db_1      | The default database encoding has accordingly been set to "UTF8".
db_1      | The default text search configuration will be set to "english".
db_1      |
db_1      | Data page checksums are disabled.
db_1      |
db_1      | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db_1      | creating subdirectories ... ok
db_1      | selecting default max_connections ... 100
db_1      | selecting default shared_buffers ... 128MB
db_1      | selecting default timezone ... Etc/UTC
db_1      | selecting dynamic shared memory implementation ... posix
db_1      | creating configuration files ... ok
db_1      | creating template1 database in /var/lib/postgresql/data/base/1 ... ok
db_1      | initializing pg_authid ... ok
db_1      | setting password ... ok
db_1      | initializing dependencies ... ok
db_1      | creating system views ... ok
db_1      | loading system objects' descriptions ... ok
db_1      | creating collations ... ok
db_1      | creating conversions ... ok
db_1      | creating dictionaries ... ok
db_1      | setting privileges on built-in objects ... ok
db_1      | creating information schema ... ok
db_1      | loading PL/pgSQL server-side language ... ok
db_1      | vacuuming database template1 ... ok
db_1      | copying template1 to template0 ... ok
db_1      | copying template1 to postgres ... ok
python    | Watching for file changes with StatReloader
python    | Performing system checks...
python    |
db_1      | syncing data to disk ...
db_1      | WARNING: enabling "trust" authentication for local connections
db_1      | You can change this by editing pg_hba.conf or using the option -A, or
db_1      | --auth-local and --auth-host, the next time you run initdb.
db_1      | ok
db_1      |
db_1      | Success. You can now start the database server using:
db_1      |
db_1      |     pg_ctl -D /var/lib/postgresql/data -l logfile start
db_1      |
db_1      | waiting for server to start....LOG:  database system was shut down at 2020-04-12 14:17:12 UTC
db_1      | LOG:  MultiXact member wraparound protections are now enabled
db_1      | LOG:  database system is ready to accept connections
db_1      | LOG:  autovacuum launcher started
python    | System check identified no issues (0 silenced).
python    | Exception in thread django-main-thread:
python    | Traceback (most recent call last):
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
python    |     self.connect()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 195, in connect
python    |     self.connection = self.get_new_connection(conn_params)
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/postgresql/base.py", line 178, in get_new_connection
python    |     connection = Database.connect(**conn_params)
python    |   File "/usr/local/lib/python3.7/site-packages/psycopg2/__init__.py", line 127, in connect
python    |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
python    | psycopg2.OperationalError: could not connect to server: Connection refused
python    |     Is the server running on host "db" (192.168.160.2) and accepting
python    |     TCP/IP connections on port 5432?
python    |
python    |
python    | The above exception was the direct cause of the following exception:
python    |
python    | Traceback (most recent call last):
python    |   File "/usr/local/lib/python3.7/threading.py", line 926, in _bootstrap_inner
python    |     self.run()
python    |   File "/usr/local/lib/python3.7/threading.py", line 870, in run
python    |     self._target(*self._args, **self._kwargs)
python    |   File "/usr/local/lib/python3.7/site-packages/django/utils/autoreload.py", line 54, in wrapper
python    |     fn(*args, **kwargs)
python    |   File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/runserver.py", line 120, in inner_run
python    |     self.check_migrations()
python    |   File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 453, in check_migrations
python    |     executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/migrations/executor.py", line 18, in __init__
python    |     self.loader = MigrationLoader(self.connection)
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/migrations/loader.py", line 49, in __init__
python    |     self.build_graph()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/migrations/loader.py", line 212, in build_graph
python    |     self.applied_migrations = recorder.applied_migrations()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/migrations/recorder.py", line 73, in applied_migrations
python    |     if self.has_table():
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/migrations/recorder.py", line 56, in has_table
python    |     return self.Migration._meta.db_table in self.connection.introspection.table_names(self.connection.cursor())
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 256, in cursor
python    |     return self._cursor()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 233, in _cursor
python    |     self.ensure_connection()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
python    |     self.connect()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/utils.py", line 89, in __exit__
python    |     raise dj_exc_value.with_traceback(traceback) from exc_value
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
python    |     self.connect()
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/base/base.py", line 195, in connect
python    |     self.connection = self.get_new_connection(conn_params)
python    |   File "/usr/local/lib/python3.7/site-packages/django/db/backends/postgresql/base.py", line 178, in get_new_connection
python    |     connection = Database.connect(**conn_params)
python    |   File "/usr/local/lib/python3.7/site-packages/psycopg2/__init__.py", line 127, in connect
python    |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
python    | django.db.utils.OperationalError: could not connect to server: Connection refused
python    |     Is the server running on host "db" (192.168.160.2) and accepting
python    |     TCP/IP connections on port 5432?
python    |
python    |
db_1      |  done
db_1      | server started
db_1      | CREATE DATABASE
db_1      |
db_1      |
db_1      | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db_1      |
db_1      | waiting for server to shut down...LOG:  received fast shutdown request
db_1      | LOG:  aborting any active transactions
db_1      | LOG:  autovacuum launcher shutting down
db_1      | .LOG:  shutting down
db_1      | LOG:  database system is shut down
db_1      |  done
db_1      | server stopped
db_1      |
db_1      | PostgreSQL init process complete; ready for start up.
db_1      |
db_1      | LOG:  database system was shut down at 2020-04-12 14:17:13 UTC
db_1      | LOG:  MultiXact member wraparound protections are now enabled
db_1      | LOG:  database system is ready to accept connections
db_1      | LOG:  autovacuum launcher started

As you can see, django container starts up in he middle of the postgres setup, and it fails to connect. If I just restart the django container (another thing, even if the runserver command failed, docker container does not think that was an error and continues working without restarting - thats I think understandable because it launches itself with StatReloader) it works of course, but I would be happy if I could have an application running from the first launch, not to restart it on purpose...

Is there anyway to prevent this from happening? Have I made any mistake here, or maybe should I take another approach on that?

Thanks in advance.

sznowak13
  • 11
  • 5
  • answer for the postgres container start first is here https://github.com/peter-evans/docker-compose-healthcheck – ANDRESMA Apr 06 '21 at 15:27

1 Answers1

0

You have to add some code that check the db status.

for example create file "wait_for_db.sh":

#!/bin/bash

echo "Waiting Postgres to launch on 5432..."

while ! nc -z db 5432; do   
    sleep 0.1 # wait for 1/10 of the second before check again
  done

  echo "Postgres launched"

then add it to your Dockerfile:

COPY wait_for_db.sh .

and change the command in your docker-compose.yml to:

command: wait_for_db.sh && python manage.py runserver 0.0.0.0:8000

good luck

E.Bloch
  • 156
  • 1
  • 5
  • Well, I wanted to avoid approaches like that, but if there is no elegant way to do this, then it might be a solution. Thanks! – sznowak13 Apr 12 '20 at 14:59