'django_1 | no port[s] to connect to' after running docker-compose up

Viewed 460

I am new to docker, and have written a containerized django app, and react app. When I go to run docker-compose up I get a weird, perpetuating error that django has no port(s) to connect to. Running the server from the react and python side both work.

Error message

enter image description here Frontend dockerfile:

COPY ./react_app/package.json .
RUN apk add --no-cache --virtual .gyp \
        python \
        make \
        g++ \
    && npm install \
    && apk del .gyp

COPY ./react_app .

ARG API_SERVER
ENV REACT_APP_API_SERVER=${API_SERVER}
RUN REACT_APP_API_SERVER=${API_SERVER} \ 
  npm run build

WORKDIR /usr/src/app
RUN npm install -g serve
COPY --from=builder /usr/src/app/build ./build
Django Python backend Dockerfile

WORKDIR /usr/src/app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt

FROM python:3.7.9-slim-stretch
RUN apt-get update && apt-get install -y --no-install-recommends netcat && \
   apt-get autoremove -y && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*
WORKDIR /usr/src/app
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh

COPY ./django_app .

RUN chmod +x /usr/src/app/entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
and the nginx dockerfile

FROM nginx:1.19.0-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

WORKDIR /usr/src/app

Django Python backend Dockerfile

WORKDIR /usr/src/app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt

FROM python:3.7.9-slim-stretch
RUN apt-get update && apt-get install -y --no-install-recommends netcat && \
   apt-get autoremove -y && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --no-cache /wheels/*
WORKDIR /usr/src/app
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh

COPY ./django_app .

RUN chmod +x /usr/src/app/entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

and the nginx dockerfile

FROM nginx:1.19.10-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

WORKDIR /usr/src/app

My entrypoint.sh

#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."
    
    while ! nc -z $DB_HOST $DB_PORT; do
      sleep 0.1
    done
    
    echo "PostgreSQL started"
fi

python manage.py collectstatic --noinput
python manage.py migrate --noinput
echo "from django.contrib.auth.models import User;
User.objects.filter(email='$DJANGO_ADMIN_EMAIL').delete();
User.objects.create_superuser('$DJANGO_ADMIN_USER', '$DJANGO_ADMIN_EMAIL', '$DJANGO_ADMIN_PASSWORD')" | python manage.py shell

exec "$@"

Docker-compose.yml

version: "3.7"

services:
  django:
    build:
      context: ./backend
      dockerfile: Dockerfile
    volumes:
      - django_static_volume:/usr/src/app/static
    expose:
      - 8000
    env_file:
      - ./backend/.env
    command: gunicorn candle.wsgi:application --bind 0.0.0.0:8000
    depends_on:
      - db
  db:
    image: postgres:12.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./postgres/.env
  react:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        - API_SERVER=${ENV_API_SERVER}
    volumes:
      - react_static_volume:/usr/src/app/build/static
    expose:
      - 3000
    env_file:
      - .env
    command: serve -s build -l 3000
    depends_on:
      - django

  nginx:
    restart: always
    build: ./nginx
    volumes:
      - django_static_volume:/usr/src/app/django_files/static
      - react_static_volume:/usr/src/app/react_files/static
    ports:
      - 80:80
    depends_on:
      - react

volumes:
  postgres_data:
  django_static_volume:
  react_static_volume:

Nginx Config

upstream django_backend {
    server django:8000;
}

upstream react_frontend {
    server react:3000;
}

server {

    listen 80;

    ###########
    # URL ROUTING #
    ###########

    location /admin {
        proxy_pass http://django_backend;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /api {
        proxy_pass http://django_backend;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    ###########
    # STATIC FOLDER ROUTING #
    ###########

    location /static/admin/ {
        alias /usr/src/app/django_files/static/admin/;
    }

    location /static/rest_framework/ {
        alias /usr/src/app/django_files/static/rest_framework/;
    }

    location /static/ {
        alias /usr/src/app/react_files/static/;
    }

    location /media/ {
        alias /usr/src/app/media/;
    }

    ###########
    # URL ROUTING #
    ###########

    location / {
        proxy_pass http://react_frontend;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }


}

I can't find anything related to this, and I also am new to Docker.

1 Answers

This is a netcat error. Specifically, it occurs when you provide a hostname but not a port while running nc.

Example:

# nc -z 8.8.8.8
no port[s] to connect to

Then, your entrypoint script sees that netcat exited with an error, and re-runs it until it succeeds, which it never will.

Debugging process

I suspected that this was a netcat problem because the entrypoint script prints "Waiting for postgres..." but not "PostgreSQL started." However, I couldn't get netcat to produce the "no port" error.

So I ran a copy of the base image, and manually installed netcat.

docker run -it python:3.7.9-slim-stretch bash
# in container
apt-get update
apt-get install netcat

I still couldn't get netcat to produce the error. So I downloaded the source code of netcat:

# still in container
apt-get source netcat

Then, armed with a copy of the source code of netcat, I found this code:

  if (argv[optind] == NULL)
    bail ("no port[s] to connect to");

Translation: If the argument parsing code, which has just finished parsing the host value, runs out of arguments, then emit the "no port[s] to connect to" error.

We can check this by running nc inside the container:

root@cc139fd40237:/source/netcat-1.10# nc -z localhost
no port[s] to connect to

Interestingly, Ubuntu has a different version of netcat, with a different error message.

$ nc -z localhost
nc: missing port number

...so that was why I couldn't reproduce the netcat error outside the container!

Robust shell scripts

In this line, either DB_HOST or DB_PORT is unset:

while ! nc -z $DB_HOST $DB_PORT; do

Besides just fixing the problem, this is a good opportunity to make your script more robust. If you add set -u to the top of your shell script, you would've gotten a vastly more helpful error message instead:

Waiting for postgres...
entrypoint.sh: 7: DB_PORT: parameter not set

You can also use the shellcheck tool - on your script, it recommends that you quote arguments:

In entrypoint.sh line 6:
    while ! nc -z $DB_HOST $DB_PORT; do
                  ^------^ SC2086: Double quote to prevent globbing and word splitting.
                           ^------^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
    while ! nc -z "$DB_HOST" "$DB_PORT"; do

which is also an excellent idea.

Related