Search code examples
djangolinuxdockercelerycookiecutter-django

File writing failing in docker production environment


In my production environments I am failing to write to files. For example, I've set up a test-task with Celery that writes the time to a file every minute:

@celery_app.task(name='print_time')
def print_time():
    now = datetime.datetime.now().strftime('%Y %b %d %a @%H:%M')
    cur_time = {"now": now}
    print(f'The date and time sent: {cur_time}')
    json.dump(cur_time, open(PATH.abspath(PATH.join(APP_DIR, "data", "cur_time.json")), "w"))
    t = json.load(open(PATH.abspath(PATH.join(APP_DIR, "data", "cur_time.json"))))
    print(f'The date and time received: {t}')

Both of the print statements will give the expected results, as of my writing this, they last printed:

The date and time sent: {'now': '2021 May 26 Wed @18:57'}
The date and time received: {'now': '2021 May 26 Wed @18:57'}

However, when I set up a view to display the contents:

class TimeView(TemplateView):
    def get_context_data(self, **kwargs):
        time = json.load(open(PATH.abspath(PATH.join(APP_DIR, "data", "cur_time.json"))))
        return time

It becomes clear that the file is not really updating in the development environment when i go to the url and the time continues to remain the same as it was when I originally rsynced the file from my development environment (which is successfully updating the file contents)

To verify this further I've also ran cat cur_time.json and stat cur_time.json to verify that the files are not being written to successfully.

Knowing that the files are not being updated, my question is two-fold. One, why are my print statements in the celery task printing the results as if the files are being updated? Two, what is the most likely cause and solution for this problem?

I was thinking it had to do with my Docker containers file writing permissions but I changed the write permissions in the data directory already by running chmod -R 777 data. Also, I haven't received any permission error messages which seem to be thrown when permissions are the issue at hand. I'm starting to hit the limits of my knowledge and wondering if anyone has any idea what the problem/solution could be. Thank you

Edit in response to comments:

I am using docker-compose. Here is my production.yml file:

version: '3'

volumes:
    production_postgres_data: {}
    production_postgres_data_backups: {}
    production_traefik: {}

services:
  django: &django
    build:
      context: .
      dockerfile: ./compose/production/django/Dockerfile
    image: myapp_production_django
    depends_on:
      - postgres
      - redis
    env_file:
      ...
    command: /start

  postgres:
    ...

  traefik:
    ...

  redis:
    image: redis:5.0

  celeryworker:
    <<: *django
    image: myapp_production_celeryworker
    command: /start-celeryworker

  celerybeat:
    <<: *django
    image: myapp_production_celerybeat
    command: /start-celerybeat

  flower:
    <<: *django
    image: myapp_production_flower
    command: /start-flower

Second edit in response to comments:

Here is a view of my local.yml file

version: '3'

volumes:
  local_postgres_data: {}
  local_postgres_data_backups: {}

services:
  django: &django
    build:
      context: .
      dockerfile: ./compose/local/django/Dockerfile
    image: myapp_local_django
    container_name: django
    depends_on:
      - postgres
    volumes:
      - .:/app:z
    env_file:
      ...
    ports:
      - "8000:8000"
    command: /start

  postgres:
    build:
      context: .
      dockerfile: ./compose/production/postgres/Dockerfile
    image: myapp_production_postgres
    container_name: postgres
    volumes:
      - local_postgres_data:/var/lib/postgresql/data:Z
      - local_postgres_data_backups:/backups:z
    env_file:
      ...

  redis:
    image: redis:5.0
    container_name: redis

  celeryworker:
    <<: *django
    image: myapp_local_celeryworker
    container_name: celeryworker
    depends_on:
      - redis
      - postgres
    ports: []
    command: /start-celeryworker

  celerybeat:
    <<: *django
    image: myapp_local_celerybeat
    container_name: celerybeat
    depends_on:
      - redis
      - postgres
    ports: []
    command: /start-celerybeat

  flower:
    <<: *django
    image: myapp_local_flower
    container_name: flower
    ports:
      - "5555:5555"
    command: /start-flower

Solution

  • To give credit where it is due. The problem and solution were elegantly put forward by @IainShelvington in the comments above.

    Reason for problem: "Any files you write in a docker container will not be written to the host machine unless you mount a volume and write to that volume."

    Solution for problem: "Add a new volume to the global "volumes:" in your compose config. Mount that volume in the "django" service, all the celery services inherit from that service so it should be shared. Write and read the files from the location that you mounted (this should be completely different from the app mount, like "/celery-logs" or something)"

    To demonstrate what this solution would look like in my specific example, I added the following to my production.yml file:

    volumes:
      ...
      production_celery: {}
    
    services:
      django: &django
        build:
          ...
        image: myapp_production_django
        depends_on:
          ...
        volumes:
          - production_celery:/app/celerydata:z
        env_file:
          ...
        command: /start
    

    Then, all data files derived from my celery scripts were sent to and pulled from the new volume/directory titled "celerydata"

    As mentioned in the comments, my app had previously been dependent on APScheduler and I had grown accustomed to quickly writing datafiles to the host machine and being able to peek through them with ease. To once again view them on the host machine and as a safety precaution (data redundancy), I started using the following command sequences to copy the files from the celerydata directory into my local machine where i can look through them with the added ease of a graphic interface:

    docker ps # note container_id == ${CID} below
    export CID=foobarbaz123
    docker cp ${CID}:/app/celerydata ./celery_storage
    

    At some point in the future I might make that into a script to run when starting up the container and will update the answer accordingly.