Permission problems with volume
In Docker, file permission issues often arise when you need to mount a host directory to a container for use as a volume. It is common that the container does not have write access to the path, which can lead to all sorts of weird problems with the service.
The reason for this type of problem is that the UIDs inside and outside the container are different. For example, host is currently using docker with a user UID of 1000 (this is the default first user UID). If the UID inside the container is 2000, then the directory created by host is not an owner for the container and is not writable by default.
There is also a case where the directory to be mounted does not exist on the host before it is mounted. Docker will create the directory with root privileges and then mount it. This leads to a situation where there are no write permissions even if both host and container have a UID of 1000. This phenomenon, which only occurs during initialisation, is enough to confuse newbies and bore veterans.
Why can’t I configure the permissions for the volume in the Dockerfile? Because a Dockerfile is a description of an image, while a volume is the contents of a container. Permissions configured in Dockerfile are valid for non-volume, but not for volume. Essentially, the directory that host mounts to a volume belongs to host. Dockerfile is executed during docker build
, while volume is generated during docker run
.
In fact, when Docker automatically creates the volume path, it should then automatically change it to the user:group
of the foreground process in the container. However, Docker does not currently have such a mechanism, so we have to find another way.
The usual temporary solution is to change the permissions manually. Either by chown, changing the owner to the UID of the user in the container, or by chmod 777, making it common to all users. These are certainly not good long-term solutions and defeat the purpose of “Docker for easy deployment”.
The best solution, for now, seems to be to customise the Dockerfile’s ENTRYPOINT
.
ENTRYPOINT
ENTRYPOINT
has the following key points.
- ENTRYPOINT specifies the default entry command for the image, which will be executed as the root command when the container is started, with all other incoming values as arguments to that command.
- The value of ENTRYPOINT can be overridden by
docker run --entrypoint
. - Only the last ENTRYPOINT command in the Dockerfile will work.
When ENTRYPOINT
is specified, the meaning of CMD
changes and instead of running its command directly, the contents of CMD are passed as an argument to the ENTRYPOINT
command. In other words the actual execution will become <ENTRYPOINT> "<CMD>"
.
So write an entry script entrypoint.sh
or docker-entrypoint.sh
in ENTRYPOINT in the dockerfile. When the container is running, use ENTRYPOINT to do something like change the permissions of the directory where the volume is mounted, and then switch to the normal user to run the normal program process.
gosu and su-exec
gosu’s github repository: https://github.com/tianon/gosu
Usage.
A simple example from the documentation.
|
|
Neither su
nor sudo
have a PID of 1 when they execute the ps aux
command, which is fine in a container, but it is not a good solution; the process with PID=1 inside the container is the application itself. So you can use the gosu
command to switch users to execute commands.
For debian, the installation is as follows.
Debian 9 (“Debian Stretch”) or newer
Older versions of Debian (or newer versions of gosu)
|
|
For alpine (3.7+)
When using Alpine it might also be worth checking out su-exec
(apk add --no-cache su-exec
), which since version 0.2 is fully compatible with gosu
at a fraction of the file size.
|
|
entrypoint scripts
Script example 1.
|
|
Description.
set -e
: If there is a command execution failure, then you should exit the script and not continue to execute further, to avoid the failure having an impact on the subsequent. This avoids the problem of the operation failing and continuing to execute.exec
: The system callexec
replaces the original process with a new one, but the PID of the process remains unchanged, ensuring that the container’s main program PID = 1.
Script example 2.
Instructions.
- If the current user is root, then create and modify the LOG_PATH directory permissions, switch to the www-data identity, take the remaining arguments and run the docker-entrypoint.sh file again (
"$0"
means docker-entrypoint.sh itself,"$@"
means the remaining arguments). If there is code elsewhere in this script that needs to be executed by the www-data user, it can be executed along with it. - When the script is executed again, since it is no longer the root user, it will simply execute
exec "$@"
, thus executing the script with the arguments, i.e. the CMD definition.
Add the docker-entrypoint.sh script to the Dockerfile and note the x
execute permission, otherwise you will not have permission to execute it.
This docker-entrypoint.sh script allows you to force directory permissions to be changed to the required permissions when the container is run, even if the volume mount directory is created by docker initialization via the root user. In this way, it is possible to run the program through a normal user in the container and write files in this normal permissions directory.