6
votes

I've been trying to find the best method to handle setting up a Django project with Docker. But I'm somewhat confused as to how CMD and ENTRYPOINT function in relation to the compose commands.

When I first set the project up, I need to run createsuperuser and migrate for the database. I've tried using a script to run the commands as the entrypoint in my Dockerfile but it didn't seem to work consistently. I switched to the configuration shown below, where I overwrite the Dockerfile CMD with commands in my compose file where it is told to run makemigrations, migrate, and createsuperuser.

The issue I'm having is exactly how to set it up so that it does what I need. If I set a command (shown as commented out in the code) in my compose file it should overwrite the CMD in my Dockerfile from what I understand.

What I'm unsure of is whether or not I need to use ENTRYPOINT or CMD in my Dockerfile to achieve this? Since CMD is overwritten by my compose file and ENTRYPOINT isn't, wouldn't it cause problems if it was set to ENTRYPOINT, since it would try to run gunicorn a second time after the compose command is executed?

Would there be any drawbacks in this approach compared to using an entrypoint script?

Lastly, is there a general best practice approach to handling Django's setup commands when deploying a dockerized Django application? Or am I already doing what is typically done?

Here is my Dockerfile:

FROM python:3.6
LABEL maintainer [email protected]

ARG requirements=requirements/production.txt
ENV DJANGO_SETTINGS_MODULE=site.settings.production_test

WORKDIR /app

COPY manage.py /app/
COPY requirements/ /app/requirements/ 

RUN pip install -r $requirements

COPY config config
COPY site site
COPY templates templates
COPY logs logs
COPY scripts scripts

EXPOSE 8001

CMD ["/usr/local/bin/gunicorn", "--config", "config/gunicorn.conf", "--log-config", "config/logging.conf", "-e", "DJANGO_SETTINGS_MODULE=site.settings.production_test", "-w", "4", "-b", "0.0.0.0:8001", "site.wsgi:application"]

And my compose file (omitted the nginx and postgres sections as they are unnecessary to illustrate the issue):

version: "3.2"
services:
  app:
    restart: always
    build:
      context: .
      dockerfile: Dockerfile.prodtest
      args:
        requirements: requirements/production.txt
    #command: bash -c "python manage.py makemigrations && python manage.py migrate && gunicorn --config gunicorn.conf --log-config loggigng.conf -e DJANGO_SETTINGS_MODULE=site.settings.production_test -W 4 -b 0.0.0.0:8000 site.wsgi"
    container_name: dj01
    environment:
      - DJANGO_SETTINGS_MODULE=site.settings.production_test
      - PYTHONDONTWRITEBYTECODE=1
    volumes:
      - ./:/app
      - /static:/static
      - /media:/media
    networks:
      - main
    depends_on:
      - db
2

2 Answers

9
votes

I have the following entrypoint script that will attempt to do the migrate automatically on my Django project:

#!/bin/bash -x

python manage.py migrate --noinput || exit 1
exec "$@"

The only change that would need to happen to your Dockerfile is to ADD it and specify the ENTRYPOINT. I usually put these lines directly about the CMD instruction:

ADD docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod a+x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

(please note that the chmod is only necessary if the docker-entrypoint.sh file on in your build environment is not executable already)

I add || exit 1 so that the script will stop the container should the migrate fail for any reason. When starting your project via docker-compose, it's possible that the database may not be 100% ready to accept connections when this migrate command runs. Between the exit on error approach and the restart: always that you have in your docker-compose.yml already, this will handle that race condition properly.

Note that the -x option I specify for bash echoes out what bash is doing, which I find helpful for debugging my scripts. It can be omitted if you want less verbosity in the container logs.

1
votes

Dockerfile:

...
ENTRYPOINT ["entrypoint.sh"]
CMD ["start"]

entrypoint.sh will be executed all the time whilst CMD will be the default argument for it (docs)

entrypoint.sh:

if ["$1" = "start"]
then
    /usr/local/bin/gunicorn --config config/gunicorn.conf \
        --log-config config/logging.conf ...
elif  ["$1" = "migrate"]
    # whatever
    python manage.py migrate
fi

now it is possible to do something like

version: "3.2"
services:
  app:
    restart: always
    build:
      ...
    command: migrate # if needed

or

docker exec -it <container> bash -c entrypoint.sh migrate