Docker Volume Permissions

Turns out that named and anonymous Docker volumes (not host mounted volumes) are always owned by root even if those files and directories already exist inside the container with different permissions. This is probably due to the Docker engine creating those virtual directories after the containers are built.

For example, the WordPress Docker image sets the owner and group of all WordPress core files to www-data when copying them to the public directory for the webserver:

sourceTarArgs=(
    --create
    --file -
    --directory /usr/src/wordpress
    --owner "$user" --group "$group"
)
targetTarArgs=(
    --extract
    --file -
)
# ...
tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
echo >&2 "Complete! WordPress has been successfully copied to $PWD"

and uses tar to copy the files to avoid overriding any existing files and directories in the destination directory which are most likely either named or virtual host volumes.

Virtual and Host Volumes

For example, a project directory is mapped to /var/www/html/wp-content/plugins/plugin-name inside a container which is also a subdirectory of a named volume wp_data:/var/www/html:

version: '3'

services:

  wordpress:
    image: wordpress:php7.3-apache
    depends_on:
      - mysql
    ports:
      - "80:80"
    volumes:
      - wp_data:/var/www/html
      - .:/var/www/html/wp-content/plugins/plugin-name
    restart: always
    environment:
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: password

# ...

The goal of the named volume wp_data is to persist the /var/www/html directory between container reboots.

Unfortunately, all files and directories created as volumes by the Docker engine (that don’t map to existing files and directories on the Docker host) are owned by root:root.

So the /var/www/html/wp-content directory inside the container ends up with the following permissions:

$ docker-compose exec wordpress ls -lah wp-content

drwxr-xr-x 4 root     root     4.0K Jul 12 08:40 .
drwxrwxrwx 6 www-data www-data 4.0K Jul 12 08:40 ..
-rw-r--r-- 1 www-data www-data   28 Jan  8  2012 index.php
drwxr-xr-x 5 root     root     4.0K Jul 12 08:40 plugins
drwxr-xr-x 5 www-data www-data 4.0K Jun 18 17:51 themes

Note that wp-content and the mapped wp-content/plugins directories are owned by root:root while the parent directory .. and wp-content/themes directories created during the container build have the correct owner www-data as set during the container build.

However, the permissions of the mapped plugin-name directory do match those on the Docker host 1000:1000 while the directory tree up until that directory is owned by root:

$ docker-compose exec wordpress ls -lah wp-content/plugins

drwxr-xr-x 5 root     root     4.0K Jul 12 09:58 .
drwxr-xr-x 4 root     root     4.0K Jul 12 09:58 ..
-rw-r--r-- 1 www-data www-data   28 Jun  5  2014 index.php
drwxr-xr-x 1     1000     1000 1.2K Jul 12 07:27 plugin-name

This prevents the application running in the container from writing to these directories since they’re owned by root.

Solution: Use Host Volumes

Instead of using named volumes wp_data:/var/www/html to persist the data, map it to a local directory ./local/public:/var/www/html instead:

version: '3'

services:

  wordpress:
    image: wordpress:php7.3-apache
    depends_on:
      - mysql
    ports:
      - "80:80"
    volumes:
      - ./local/public:/var/www/html
      - .:/var/www/html/wp-content/plugins/plugin-name
    restart: always
    environment:
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: password

# ...

which will make it inherit the owner of the directory on the Docker host:

$ docker-compose exec wordpress ls -lah wp-content

total 4.0K
drwxr-xr-x 1 1000 1000 224 Jul 12 08:57 .
drwxr-xr-x 1 1000 1000 768 Jul 12 09:04 ..
-rw-r--r-- 1 1000 1000  28 Jan  8  2012 index.php
drwxr-xr-x 1 1000 1000 224 Jul 12 08:55 plugins
drwxr-xr-x 1 1000 1000 224 Jul 12 08:57 themes
drwxr-xr-x 1 1000 1000  64 Jul 12 08:57 upgrade
drwxr-xr-x 1 1000 1000  96 Jul 12 08:57 uploads

Now all files are owned by the user ID 1000 with full permissions.

1 Comment

  1. Owen says:

    Same problem in postgres container. Docker compose should allow to set permission when mounting the volumes.

Leave a Reply