dockerdocker-composedockerfiledocker-volume

Can docker compose volume create the entire path as non-root user?


I created a user with uid 1000 in the Dockerfile and created a directory /app, changing the owner of /app to uid 1000. Spring Boot also starts using the user with uid 1000.

RUN adduser --uid 1000 -D appuser
RUN mkdir /app
RUN chown -R appuser:appuser /app
USER 1000
ENTRYPOINT [ "./docker-entrypoint.sh" ]

When Spring Boot starts, it creates the directory /app/portal/logs and writes logs to the logs directory. Before using Docker Compose to mount the logs to a directory on the host with a volume, the entire path /app/portal/logs had uid 1000 as the owner, and there was no issue.

However, when I created a volume to mount /app/portal/logs to /data on the host, the owner of the app and logs directories inside the container remained uid 1000, but the owner of the intermediate directory, portal, became root.

services:
  portal:
    image: myapp:latest
    volumes:
    - /data:/app/portal/logs
    user: "1000:1000"
/app $ ls -al / | grep app
drwxr-xr-x    1 appuser  appuser         83 Jun  7 16:13 app

/app $ ls -al /app | grep portal
drwxr-xr-x    3 root     root            18 Jun  7 16:13 portal

/app $ ls -al /app/portal/
drwxr-xr-x    3 root     root            18 Jun  7 16:13 .
drwxr-xr-x    1 appuser  appuser         83 Jun  7 16:13 ..
drwxr-xr-x    2 appuser  appuser       4096 Jun  7 14:54 logs

This causes Spring Boot to have insufficient permissions when creating other directories in /app/portal. How can I change the owner of the entire path created by the volume to uid 1000?

I tried adding user: "1000:100" in the Docker Compose YAML, but it didn't work. Pre-creating /app/portal and changing the owner of the entire path to uid 1000 in the Dockerfile beforehand can solve this issue, but it seems a bit odd to do so.


Solution

  • Docker is automatically creating the directory for you, with a set of permissions that don't match what you want. You probably need to either mount that parent directory as a volume or create it in the Dockerfile, or both; mounting the parent will make the permissions easier.

    volumes:
      - /data:/app/portal
      #       ^^^^^^^^^^^ for the entire writable directory
    user: 1000:1000  # matching the numeric owner for this directory on this system
    

    Docker needs to create the container filesystem before it can start the process. You clarify in a comment that the /app/portal directory isn't in the image. But, your Compose file mounts content onto /app/portal/logs. That means Docker needs to create the mount point, which also means it needs to create the directory that holds it.

    At that point Docker needs to choose an owner and permissions. "Owned by root" is one of several reasonable choices it could make, but it happens to not work for your application.

    I mentioned that you can create the directory in the image too. That's probably a good idea

    RUN adduser -D appuser
    RUN mkdir /app/portal && chown appuser:appuser /app/portal
    

    In your current Dockerfile, doing this will avoid the permission problem: the parent directory will exist and its owner in the image happens to match the user: in the Compose file. In general, though, you can't guarantee that the runtime user: will exactly match what's built into the image. In this last fragment I've removed the specific numeric user ID from the Dockerfile; but that means you will always need to mount a volume over the entire writable directory if you're going to use a non-default user:.