In order to periodically execute tasks in my Django app I have installed django_crontab
extension. My app consists of both dockerized database and Django app.
https://pypi.org/project/django-crontab/
I have done every step as it is described in setup paragraph.
settings.py
INSTALLED_APPS = [
...
'django_crontab',
]
...
CRONJOBS = [
('*/1 * * * *', 'config.cron.fun')
]
cron.py
def fun():
print("hello cron")
with open("./test.txt", "a") as f:
f.write("Hello")
I also add cron job in docker-entrypoint.yml
:
python manage.py crontab add
python manage.py crontab show
output:
webapp | no crontab for root
webapp | adding cronjob: (ebcca28ea3199afe6d09a445db5d5fd8) -> ('*/1 * * * *', 'config.cron.fun')
webapp | Currently active jobs in crontab:
webapp | ebcca28ea3199afe6d09a445db5d5fd8 -> ('*/1 * * * *', 'config.cron.fun')
In Dockerfile
I use python:3.8
image and install cron
:
RUN apt-get install -y cron && touch /var/log/cron.log
And no response after running the container.
When I enter the container, I can see that cron sees the job, but still does not execute it.
root@bar:/back# crontab -l
*/1 * * * * /usr/local/bin/python /back/manage.py crontab run ebcca28ea3199afe6d09a445db5d5fd8 # django-cronjobs for config
How can I fix it?
EDIT: Whole Dockerfile:
FROM python:3.8
ENV PYTHONUNBUFFERED 1
RUN mkdir /back
WORKDIR /back
COPY . /back/
RUN apt-get update
RUN apt-get install -y cron && touch /var/log/cron.log
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ENTRYPOINT ["python3", "manage.py"]
CMD ["runserver", "0.0.0.0:8080"]
docker-compose.yml
:
version: "3"
services:
db:
image: postgres:latest
restart: unless-stopped
container_name: database
env_file:
- .env
webapp:
build: ./back
container_name: webapp
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
links:
- db:db
volumes:
- ./back:/back
entrypoint: "bash /back/docker-entrypoint.sh"
command: "runserver 0.0.0.0:8000"
docker-entrypoint.yml
- here are made migrations, waiting for db
and mentioned crontab
commands
CodePudding user response:
A Docker container only runs one process. You need to run two separate things: the main application server, and (after some initial setup) the cron daemon. You can straightforwardly run two separate containers running two separate commands from the same image, with the correct setup.
The first change I would make here is to combine what you currently have as a split ENTRYPOINT
and CMD
into a single CMD
:
# no ENTRYPOINT, but
CMD ["python3", "manage.py", "runserver", "0.0.0.0:8080"]
With this setup, you can launch one container using this default command, and a second command from the same image but overriding command:
in the Compose setup.
version: '3.8'
services:
db: { ... }
webapp:
build: ./back
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
# run the default command: from the image; no override
# skip unnecessary links:, container_name:, volumes: options
cron:
build: ./back # same as main application
restart: unless-stopped
env_file:
- .env
depends_on:
- db
command: cron -f # as a long-running foreground process
The thing we haven't done here is populate the crontab at container startup. This is where ENTRYPOINT
can come in: if you have an ENTRYPOINT
, it gets run instead of the CMD
, but it gets passed the CMD
as arguments. A pretty typical pattern here is to use ENTRYPOINT
for a shell script that does some first-time setup, and then ends with exec "$@"
to run whatever the provided CMD
/command:
was.
#!/bin/sh
# docker-entrypoint.sh
# If this is going to be a cron container, set up the crontab.
if [ "$1" = cron ]; then
./manage.py crontab add
fi
# Launch the main container command passed as arguments.
exec "$@"
Then in the Dockerfile, specify this script as the ENTRYPOINT
.
...
WORKDIR /back
COPY . ./ # including docker-entrypoint.sh
...
ENTRYPOINT ["./docker-entrypoint.sh"] # must be JSON-array syntax
CMD ["./manage.py", "runserver", "0.0.0.0:8080"] # as before
You don't need to override entrypoint:
here. Commands like docker-compose run web app bash
to get an interactive debugging shell will work fine; they'll go via that entrypoint script, and run the shell as the final exec "$@"
line.