Permission denied on mkdir inside of a django Docker container when running collectstatic
Asked Answered
S

1

8

I've modified django-cookiecutter default production template to make caddy web server serve static files. I'm using volumes to map the ./static directories in django and caddy containters through host ./static directory, but I'm getting permissions error when docker executes python manage.py collectstatic --noinput while trying to create a subfolder of ./static.

However, if I don't switch to django user in django container's Dockerfile, hence execute collectstatic as root, everything works perfectly. I guess django user in the container is not allowed to write to host directory, even despite the fact that chown -R django /app/static was successfully executed.

Traceback (most recent call last):
File "/app/manage.py", line 30, in <\module>
execute_from_command_line(sys.argv)
...
File "/usr/local/lib/python3.6/site-packages/collectfast/management/commands/collectstatic.py", line 111, in copy_file
self.do_copy_file(args)
File "/usr/local/lib/python3.6/site-packages/collectfast/management/commands/collectstatic.py", line 100, in do_copy_file
path, prefixed_path, source_storage)
File "/usr/local/lib/python3.6/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 354, in copy_file
self.storage.save(prefixed_path, source_file)
File "/usr/local/lib/python3.6/site-packages/django/core/files/storage.py", line 49, in save
return self._save(name, content)
File "/usr/local/lib/python3.6/site-packages/django/core/files/storage.py", line 236, in _save
os.makedirs(directory)
File "/usr/local/lib/python3.6/os.py", line 220, in makedirs
mkdir(name, mode)
PermissionError:
[Errno 13] Permission denied: '/app/static/sass'

I tried chown -R systemd-timesync:root static inside host, creating ./static folder beforehand inside host as root, and adding RUN mkdir /app/static && chown -R django /app/static to django container's Dockerfile (to execute as container's root user).

docker-compose.yml

version: '3'

volumes:
  production_postgres_data: {}
  production_postgres_data_backups: {}
  production_caddy: {}

services:
  django:
    build:
      context: .
      dockerfile: ./compose/production/django/Dockerfile
    volumes:
      - ./static:/app/static
    depends_on:
      - postgres
      - redis
    env_file:
      - ./.envs/.production/.django
      - ./.envs/.production/.postgres
    command: /start

  postgres:
    build:
      context: .
      dockerfile: ./compose/production/postgres/Dockerfile
    volumes:
      - production_postgres_data:/var/lib/postgresql/data
      - production_postgres_data_backups:/backups
    env_file:
      - ./.envs/.production/.postgres

  caddy:
    build:
      context: .
      dockerfile: ./compose/production/caddy/Dockerfile
    depends_on:
      - django
    volumes:
      - production_caddy:/root/.caddy
      - ./static:/srv/static
    env_file:
      - ./.envs/.production/.caddy
    ports:
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"

  redis:
    image: redis:3.2

django container Dockerfile

FROM nickgryg/alpine-pandas

ENV PYTHONUNBUFFERED 1

RUN apk update \
  # psycopg2 dependencies
  && apk add --virtual build-deps gcc python3-dev musl-dev \
  && apk add postgresql-dev \
  # Pillow dependencies
  && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
  # CFFI dependencies
  && apk add libffi-dev py-cffi \
  # lxml dependencies
  && apk add libxml2-dev libxslt-dev

RUN addgroup -S django \
    && adduser -S -G django django

# Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements
RUN pip install --no-cache-dir -r /requirements/production.txt \
    && rm -rf /requirements

COPY ./compose/production/django/entrypoint /entrypoint
RUN sed -i 's/\r//' /entrypoint
RUN chmod +x /entrypoint
RUN chown django /entrypoint

COPY ./compose/production/django/start /start
RUN sed -i 's/\r//' /start
RUN chmod +x /start
RUN chown django /start

COPY . /app

RUN chown -R django /app

USER django

WORKDIR /app

ENTRYPOINT ["/entrypoint"]

django container start script

#!/bin/sh

set -o errexit
set -o pipefail
set -o nounset

python /app/manage.py collectstatic --noinput
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app

I don't want my container to be executed as root, so I'm looking for any solutions / ideas.

Softball answered 19/11, 2018 at 0:29 Comment(2)
Are you using Whitenoise or not? How are you settings for static files? The ones in the prod config here: github.com/pydanny/cookiecutter-django/blob/…Roscoe
@BrunoA. no, I'm not, thanks for the insight, I will consider using it! I forked from cookiecutter before Whitenoise was added, so I just replaced AWS lines with local static folder, and then serve them with caddy webserver by using "except /static"Softball
S
4

Finally found a workaround other than executing collectstatic as root. As I suspected, the problem was in docker's permissions, and we should grant Docker the permissions to create folders in /static folder, which is owned by django user inside django Docker container. We can do that, knowing that userId is the same between host system and the container, by running

docker-compose run django id -u django

It outputs the userId of the django user in the system. For instance, uid is 100. Then run (not sure about gid, but it works when gid = uid + 1)

chown -R 100:101 /static

If we run ls -lh, we can see that static folder is owned by systemd-network, which is sort of a Docker user mapped to uid = 100

drwxr-xr-x  4 root            root            4.0K Sep 27 11:23 compose
drwxr-xr-x  3 root            root            4.0K Nov 27 12:09 config
drwxr-xr-x  3 root            root            4.0K Nov 14 02:04 docs
drwxr-xr-x  2 root            root            4.0K Sep 27 11:23 locale
-rwxr-xr-x  1 root            root            1.1K Sep 27 12:56 manage.py
...
drwxr-xr-x 11 systemd-network systemd-journal 4.0K Nov 21 22:15 static
drwxr-xr-x  2 root            root            4.0K Nov 27 13:37 utils

It should solve the problem. Beware that after rebuilding the container uid of django user may change, and the error will appear again, so you would have to repeat this.

Everyone who understands a bit more how Docker works is welcome to explain what happens here, and I will accept his answer.

Softball answered 29/11, 2018 at 12:10 Comment(1)
This fix unfortunately does not work if your application model(s) contain file fields, since every file uploaded will also have root as the ownerFeat

© 2022 - 2024 — McMap. All rights reserved.