Compose: Why does use of "volumes" directive ruin directory ownership?

Created on 23 Dec 2017  ยท  11Comments  ยท  Source: docker/compose

Hi,

When I have volumes: directive set in docker-compose.yml, the ownership of the logs directory is root:root. If I get rid of volumes: directive then the ownership of the logs directory is root:www-data which is what I want but this time I won't be able share logs with my local OS. How can I make this possible? So all I need is, own /usr/local/apache2/logs as root:www-data and see the logs in local OS. That's all!

Thanks

$ docker-compose -v
docker-compose version 1.17.0, build ac53b73
$ docker -v
Docker version 17.09.0-ce, build afdb6d4

docker-compose.yml

version: '3'
services:
    apache_img:
        container_name: apache_con
        build: ./apache
        volumes:
            - ../logs/apache:/usr/local/apache2/logs

apache/Dockerfile

FROM httpd:2.4
RUN chown -R root:www-data /usr/local/apache2/logs

Result from having volumes: directive

$ docker exec -it apache_con bash

root@6063c694ef2b:/usr/local/apache2# ls -l 
...
drwxr-sr-x 1 root www-data 4096 Dec 23 09:40 conf
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 htdocs
drwxr-xr-x 2 root root     4096 Dec 23 09:40 logs

root@6063c694ef2b:/usr/local/apache2# ls -l logs/
...
-rw-r--r-- 1 root root 0 Dec 23 09:47 access.log
-rw-r--r-- 1 root root 0 Dec 23 09:47 error.log

Result from not having volumes: directive

ubuntu@linux:~/hello-world$ docker exec -it apache_con bash

root@9a5da32a0557:/usr/local/apache2# ls -l
...
drwxr-sr-x 1 root www-data 4096 Dec 23 09:47 conf
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 htdocs
drwxr-sr-x 1 root www-data 4096 Dec 23 09:47 logs

root@9a5da32a0557:/usr/local/apache2# ls -l logs/
...
-rw-r--r-- 1 root www-data 0 Dec 23 09:47 access.log
-rw-r--r-- 1 root www-data 0 Dec 23 09:47 error.log
kinquestion

Most helpful comment

This is a bit of a combination of issues.

I'll try to explain the "permissions" issue first.

When using the <host-path>:<container-path> for a volume, this is actually not a "volume", but a bind-mounted host-directory (a "bind mount" for short).

Bind mounts are a way to express that you allow a container to have access to a path on the host, and (in case the host-path is a directory) all the files that are in there, and with the permissions and ownership of those files on the host (they're _literally_ the same files as the files on the host, mounted into the container). So what you're seeing is not a change in permissions of the actual files in the container, but the permissions and ownership of the files/directories on the host, and that are mounted in the container.

Here's where the problem is; somewhere in the docker history, a feature was added to automatically _create_ the host path if the path didn't exist ("let's make this easier to use"). In hindsight, this was a bad idea for various reasons;

  • The path on the host may not be found because you made a typo in the path. Instead of producing an error, docker now creates the path.
  • The "host" is the host where the docker _daemon_ runs, which may be a remote machine (or a local VM). If the path is missing, docker creates the path on the remote host. Docker for Mac and Docker for Windows have some magic to make this path also appear on the local machine, but for other situations, this may be unexpected.
  • If the host path is missing, docker doesn't know if you intended to mount a _directory_ or a _file_. If you intended to bind-mount a _file_ (e.g. ./apache.conf:/some/path/apache.conf), and the file doesn't exist, docker will create a _directory_ named apache.conf and try to bind-mount that (causing hard-to-find issues).
  • If the host path is missing, docker doesn't know what permissions and ownership it should have, so it uses the default ownership/permissions from the Docker daemon process (which runs as root)

The last one is the issue you're running into:

  • The local logs/apache directory didn't exist before you started the container
  • Docker creates a directory using the default ownership (root:root)
  • Docker bind-mounts the directory into the container.

To make sure the bind-mounted directory has the correct permissions;

  • create the directory on the host
  • set the correct ownership and permissions
  • ... _before_ starting the container

Inside the container, find out the UID/GID of the www-data user;

$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Exit the container, and remove the compose stack;

$ docker-compose down
Stopping apache_con ... done
Removing apache_con ... done
Removing network repro5507_default

Create the host-directory, and change its permissions;

$ mkdir -p logs/apache
$ sudo chown -R 0:33 logs/apache

Start your docker compose stack

$ docker-compose up -d
...
Creating network "repro5507_default" with the default driver
Creating apache_con ... done

And you'll see that the permissions/ownership matches what's on the host;

$ docker exec apache_con ls -la
total 48
drwxr-sr-x 1 www-data www-data 4096 Dec 12 04:59 .
drwxrwsr-x 1 root     staff    4096 Dec 12 04:57 ..
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 bin
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 build
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 cgi-bin
drwxr-sr-x 4 root     www-data 4096 Dec 12 04:59 conf
drwxr-sr-x 3 root     www-data 4096 Dec 12 04:59 error
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 htdocs
drwxr-sr-x 3 root     www-data 4096 Dec 12 04:59 icons
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 include
drwxr-xr-x 2 root     www-data 4096 Dec 25 14:25 logs
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 modules

Specific to logging

Having explained the permissions issue above, your question is also around _logging_. When using containers, the recommended approach is to have your container's process not log to _files_, but to stdout and stderr, and use Docker's logging drivers to capture the logs.

If you look at the Dockerfile for the httpd image, you'll find these lines;

    sed -ri \
        -e 's!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g' \
        -e 's!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g' \
        "$HTTPD_PREFIX/conf/httpd.conf"; \

These lines configure Apache to send logs to /proc/self/fd/1 and /proc/self/fs/2, which are stdout and stderr. This means that logs are not sent to a file.

If you run a httpd (Apache) container, the output of stdout and stderr are caught by Docker's logging driver, and you can view those logs using the docker container logs command (or, with Docker Compose; docker-compose logs <service name>)

$ docker-compose logs apache_img
Attaching to apache_con
apache_con    | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
apache_con    | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
apache_con    | [Mon Dec 25 14:25:45.740722 2017] [mpm_event:notice] [pid 1:tid 140131754698624] AH00489: Apache/2.4.29 (Unix) configured -- resuming normal operations
apache_con    | [Mon Dec 25 14:25:45.740900 2017] [core:notice] [pid 1:tid 140131754698624] AH00094: Command line: 'httpd -D FOREGROUND'

This means that you should _not_ create a volume for that directory, but have docker handle the logs.

More information on logging can be found in the userguide; https://docs.docker.com/engine/admin/logging/view_container_logs/

All 11 comments

This is a bit of a combination of issues.

I'll try to explain the "permissions" issue first.

When using the <host-path>:<container-path> for a volume, this is actually not a "volume", but a bind-mounted host-directory (a "bind mount" for short).

Bind mounts are a way to express that you allow a container to have access to a path on the host, and (in case the host-path is a directory) all the files that are in there, and with the permissions and ownership of those files on the host (they're _literally_ the same files as the files on the host, mounted into the container). So what you're seeing is not a change in permissions of the actual files in the container, but the permissions and ownership of the files/directories on the host, and that are mounted in the container.

Here's where the problem is; somewhere in the docker history, a feature was added to automatically _create_ the host path if the path didn't exist ("let's make this easier to use"). In hindsight, this was a bad idea for various reasons;

  • The path on the host may not be found because you made a typo in the path. Instead of producing an error, docker now creates the path.
  • The "host" is the host where the docker _daemon_ runs, which may be a remote machine (or a local VM). If the path is missing, docker creates the path on the remote host. Docker for Mac and Docker for Windows have some magic to make this path also appear on the local machine, but for other situations, this may be unexpected.
  • If the host path is missing, docker doesn't know if you intended to mount a _directory_ or a _file_. If you intended to bind-mount a _file_ (e.g. ./apache.conf:/some/path/apache.conf), and the file doesn't exist, docker will create a _directory_ named apache.conf and try to bind-mount that (causing hard-to-find issues).
  • If the host path is missing, docker doesn't know what permissions and ownership it should have, so it uses the default ownership/permissions from the Docker daemon process (which runs as root)

The last one is the issue you're running into:

  • The local logs/apache directory didn't exist before you started the container
  • Docker creates a directory using the default ownership (root:root)
  • Docker bind-mounts the directory into the container.

To make sure the bind-mounted directory has the correct permissions;

  • create the directory on the host
  • set the correct ownership and permissions
  • ... _before_ starting the container

Inside the container, find out the UID/GID of the www-data user;

$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Exit the container, and remove the compose stack;

$ docker-compose down
Stopping apache_con ... done
Removing apache_con ... done
Removing network repro5507_default

Create the host-directory, and change its permissions;

$ mkdir -p logs/apache
$ sudo chown -R 0:33 logs/apache

Start your docker compose stack

$ docker-compose up -d
...
Creating network "repro5507_default" with the default driver
Creating apache_con ... done

And you'll see that the permissions/ownership matches what's on the host;

$ docker exec apache_con ls -la
total 48
drwxr-sr-x 1 www-data www-data 4096 Dec 12 04:59 .
drwxrwsr-x 1 root     staff    4096 Dec 12 04:57 ..
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 bin
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 build
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 cgi-bin
drwxr-sr-x 4 root     www-data 4096 Dec 12 04:59 conf
drwxr-sr-x 3 root     www-data 4096 Dec 12 04:59 error
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 htdocs
drwxr-sr-x 3 root     www-data 4096 Dec 12 04:59 icons
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 include
drwxr-xr-x 2 root     www-data 4096 Dec 25 14:25 logs
drwxr-sr-x 2 root     www-data 4096 Dec 12 04:59 modules

Specific to logging

Having explained the permissions issue above, your question is also around _logging_. When using containers, the recommended approach is to have your container's process not log to _files_, but to stdout and stderr, and use Docker's logging drivers to capture the logs.

If you look at the Dockerfile for the httpd image, you'll find these lines;

    sed -ri \
        -e 's!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g' \
        -e 's!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g' \
        "$HTTPD_PREFIX/conf/httpd.conf"; \

These lines configure Apache to send logs to /proc/self/fd/1 and /proc/self/fs/2, which are stdout and stderr. This means that logs are not sent to a file.

If you run a httpd (Apache) container, the output of stdout and stderr are caught by Docker's logging driver, and you can view those logs using the docker container logs command (or, with Docker Compose; docker-compose logs <service name>)

$ docker-compose logs apache_img
Attaching to apache_con
apache_con    | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
apache_con    | AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.18.0.2. Set the 'ServerName' directive globally to suppress this message
apache_con    | [Mon Dec 25 14:25:45.740722 2017] [mpm_event:notice] [pid 1:tid 140131754698624] AH00489: Apache/2.4.29 (Unix) configured -- resuming normal operations
apache_con    | [Mon Dec 25 14:25:45.740900 2017] [core:notice] [pid 1:tid 140131754698624] AH00094: Command line: 'httpd -D FOREGROUND'

This means that you should _not_ create a volume for that directory, but have docker handle the logs.

More information on logging can be found in the userguide; https://docs.docker.com/engine/admin/logging/view_container_logs/

First of all thanks for the detailed explanation!

Whether I manually create folders or not and grand specific permissions before running docker-compose command, sadly my settings don't remain intact. They become root:root again so docker doesn't obey anything. Let's ignore it for a minute because I tried this in many different combinations so far.

If I was docker, this is how I would behave:

  • Do the folders exist on host OS? (_the ones defined in volumes directive_)

    • Yes: Do not touch permissions at all. Leave everything intact.

    • No: Create folders and make the current host OS user (_the one who runs the docker-compose command_) owner of the folders, not the root.

because I don't like seeing root all over the place.

This is how temporarily solved the problem but I don't consider this as a concrete solution. Docker should handle this. Although the config below is for PHP, same thing applies to apache too so just showing as an example.

Dockerfile

...

RUN usermod -u 1000 www-data \
 && groupmod -g 1000 www-data

build.sh

#!/bin/bash
set -e

docker-compose up -d
docker exec php_container chown -R root:www-data /usr/local/etc/logs

Based on above, folders and files are owned as shown below - owner:group.

Container:

/usr/local/etc/logs: www-data www-data
/usr/local/etc/logs/error.log: www-data www-data

Local:

logs/php: ubuntu ubuntu
logs/php/error.log: ubuntu ubuntu

Whether I manually create folders or not and grand specific permissions before running docker-compose command, sadly my settings don't remain intact

What platform / setup are you running on? Are you running on OS X? Using Docker for Mac, or Docker Toolbox? If so, the situation is slightly different, because your local directory is not what's being shared with the container.

Create folders and make the current host OS user (the one who runs the docker-compose command

The daemon (which is what creates the directory/host path) has no knowledge about that. The only connection between docker-compose and the daemon is the remote API, which does not carry "who made the API call"

I am on Ubuntu 16.04, no Toolbox. Everything is as native as they get. Plain docker, plain compose, plain Ubuntu.

I'm not able to reproduce that problem; here's the steps I took (based on your _first_ description at the top of this issue), running on an Ubuntu 16.04 machine;

First create directories, docker-compose file and Dockerfile;

mkdir -p repro-5507 && cd repro-5507

mkdir -p compose/apache logs/apache


cat > compose/apache/Dockerfile <<EOF
FROM httpd:2.4
RUN chown -R root:www-data /usr/local/apache2/logs
EOF

cat > compose/docker-compose.yml <<EOF
version: '3'
services:
    apache_img:
        container_name: apache_con
        build: ./apache
        volumes:
            - ../logs/apache:/usr/local/apache2/logs
EOF

Which should now look like this;

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507$ tree
.
โ”œโ”€โ”€ compose
โ”‚ย ย  โ”œโ”€โ”€ apache
โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ Dockerfile
โ”‚ย ย  โ””โ”€โ”€ docker-compose.yml
โ””โ”€โ”€ logs
    โ””โ”€โ”€ apache

4 directories, 2 files

And permissions for the logs/apache directory;

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507$ ls -la logs/
total 12
drwxrwxr-x 3 ubuntu ubuntu 4096 Dec 29 10:41 .
drwxrwxr-x 4 ubuntu ubuntu 4096 Dec 29 10:41 ..
drwxrwxr-x 2 ubuntu ubuntu 4096 Dec 29 10:41 apache

Now, change to the compose directory

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507$ cd compose/

And bring up the compose stack;

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507/compose$ docker-compose up -d
Creating network "compose_default" with the default driver
Building apache_img
Step 1/2 : FROM httpd:2.4
2.4: Pulling from library/httpd
f49cf87b52c1: Pull complete
02ca099fb6cd: Pull complete
de7acb18da57: Pull complete
770c8edb393d: Pull complete
0e252730aeae: Pull complete
6e6ca341873f: Pull complete
2daffd0a6144: Pull complete
Digest: sha256:b5f21641a9d7bbb59dc94fb6a663c43fbf3f56270ce7c7d51801ac74d2e70046
Status: Downloaded newer image for httpd:2.4
 ---> 7239615c0645
Step 2/2 : RUN chown -R root:www-data /usr/local/apache2/logs
 ---> Running in 8467f222adc3
Removing intermediate container 8467f222adc3
 ---> 77f46ced55fa
Successfully built 77f46ced55fa
Successfully tagged compose_apache_img:latest
WARNING: Image for service apache_img was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating apache_con ... done

Check permissions of the logs directory inside the container (1000:1000 matches the uid:gid of the "ubuntu" user on the host);

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507/compose$ docker exec apache_con ls -l /usr/local/apache2/
total 40
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 bin
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 build
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 cgi-bin
drwxr-sr-x 4 root www-data 4096 Dec 12 04:59 conf
drwxr-sr-x 3 root www-data 4096 Dec 12 04:59 error
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 htdocs
drwxr-sr-x 3 root www-data 4096 Dec 12 04:59 icons
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 include
drwxrwxr-x 2 1000     1000 4096 Dec 29 10:47 logs
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 modules

Change permissions of the logs directory on the host to ubuntu:www-data (uid/gid: 1000:33);

ubuntu@ubuntu-2gb-ams3-01:~/repro-5507/compose$ sudo chown -R 1000:33 ../logs/apache/

Check permissions again inside the container;

root@ubuntu-2gb-ams3-01:/home/ubuntu/repro-5507/compose# docker exec apache_con ls -l /usr/local/apache2/
total 40
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 bin
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 build
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 cgi-bin
drwxr-sr-x 4 root www-data 4096 Dec 12 04:59 conf
drwxr-sr-x 3 root www-data 4096 Dec 12 04:59 error
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 htdocs
drwxr-sr-x 3 root www-data 4096 Dec 12 04:59 icons
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 include
drwxrwxr-x 2 1000 www-data 4096 Dec 29 10:53 logs
drwxr-sr-x 2 root www-data 4096 Dec 12 04:59 modules

I think I got lost in too many things I tried and probably gave you a wrong impression. I've just did what you did above and the result is same. I'll have to go with your solution because not having local folder messes up everything so I shall manually create logs folder and get on with it. Thanks for taking all that time to answer my question.

Good to hear my input helped you. While the documentation around this was improved recently, I think we can still use some additional information to explain the differences (in "semantics" and "behaviour") between "volumes" and "bind mounts" a bit more in-depth; https://docs.docker.com/engine/admin/volumes/bind-mounts/ (and https://docs.docker.com/engine/admin/volumes/).

I have not found the time to work on that, but it may be good to open an issue in the documentation issue tracker (perhaps you can open one? https://github.com/docker/docker.github.io/issues)

@thaJeztah I've just created one and hope that it makes sense or addresses what you meant. Please let me know if it needs specific update to make it clearer.

Thanks! I'll try to have a look soon (but don't hesitate to "ping" me if I don't get round to it :innocent:)

Hi. I see this issue is pretty old but not closed yet. I am currently having strange issue. I am using docker-compose to create and spawn a locally running application I am developing. The idea here is that if I modify any file this is bind-mounted to the docker image and the changes are available to it without the need of restarting it. I.e., I change a JS file, hit refresh in my browser and the change is present.

My docker-compose.yaml looks like this:

volumes:
  - ..:/var/www/my-app
  - /tmp

The reason for .. is that my docker-compose.yaml is inside of /var/www/my-app/docker folder.

Now the problem I am facing is that all files have 0644 permission set and the owner is myuser:my-domain which lets me edit the files without a problem. But if I run from within my app root

docker-compose -f docker/docker-compose.yaml my-app-dev up

all my files user owner is changed to www-data. Inside the image, there is a supervisor process which runs nginx and php-fpm processes beneath. They both are using www-data user and group.

Since the user owner of all my files changes to www-data (keeping the my-domain group) and the permissions are 0644, I am unable to edit the files anymore until running sudo chown -R my-user:my-domain ./`. The owner remains changed even if after I stop the container.

Is this expected behavior? Or am I doing something wrong? I cannot change the permission of the files to 0755 or 0777 as doing so would require to commit them all to Git which is definitely not desired.

If this is all setup and behaving correctly, then I do not have other choice than to build the images manually and for each change during the development re-build and restart the docker image, which is also not desired.

Many thanks for your help!

Hi. I see this issue is pretty old but not closed yet.

Well.. it's closed, but not "locked"

screen shot 2018-11-12 at 19 48 55

all my files user owner is changed to www-data. Inside the image, there is a supervisor process which runs nginx and php-fpm processes beneath. They both are using www-data user and group.

Note that docker itself won't change permissions of files that are bind-mounted from the host. Is there perhaps an (entrypoint-)script in your container that sets permissions?

Is this expected behavior? Or am I doing something wrong?

It's not expected if docker would do this, but (as mentioned) perhaps it's something in your container that's setup to set permissions?

Please keep in mind that the GitHub issue tracker is not intended as a general support forum,
but for reporting bugs and feature requests. For other type of questions, consider using one of;

Was this page helpful?
0 / 5 - 0 ratings