Understanding Linux file permissions in Docker containers
• Author: Agustín Ramiro Díaz •Table of Contents
Motivation
I recently wanted to work in the surrealdb codebase, and by looking at its issues I’ve found one related to file permissions in Docker. I remember having some vague knowledge about issues with file permissions before, and I thought it would be a good time to clear my doubts.
Understanding file permissions
File permissions in Linux
I won’t go into too much detail because there are great resources that explain this better than me. Heres’s a brief summary:
In Linux, file permissions are managed by the kernel. Each file has an owner, a group, and a set of permissions. The owner is the user who created the file, and the group is a set of users who have the same permissions over the file. The permissions are divided into three categories: read, write, and execute. Each category has three possible values: allowed, denied, and not set. The permissions are represented by a 9-character string, where the first three characters represent the owner’s permissions, the next three characters represent the group’s permissions, and the last three characters represent the permissions for everyone else.
This permissions are usually handled with numbers or leters when running commands like chmod
. The numbers are octal, and the letters are r
for read, w
for write, and x
for execute. The numbers are calculated by adding the values of the permissions: 4 for read, 2 for write, and 1 for execute. For example, if we want to give read and write permissions to the owner, and read permissions to the group and everyone else, we would run chmod 644 file.txt
. This would set the permissions to -rw-r--r--
. We can also use the letters to set the permissions, for example, chmod u+x file.txt
would add the execute permission to the owner.
File permissions in Docker containers
Docker containers are isolated environments, but they still run on top of the host’s kernel. This means that the file permissions are managed by the host’s kernel, and the container’s kernel is not involved in this process. This is important to understand because it means that the file permissions inside the container are the same as the file permissions on the host. This can lead to issues for example when the container and the host have different users and groups.
By default, the container user is set to root
with uid and gid 0. This means that if we create a file inside the container, root
will be the owner of the file. This can lead to issues when we try to access the file from the host, because the host’s user might not have the same uid as the container’s root
. This can be solved by using the --user
flag when running the container, which allows us to specify the uid and gid of the container’s user. This way, we can make sure that the container’s user has the same uid and gid as the host’s user.
Example with default root
user:
# run a container which creates a file
# check the file permissions
Example with custom user:
# if you are following along, you'll need to delete the file generated previously
# run a container which creates a file with the current user. $(id -u) and $(id -g) are used to get the current user's uid and gid
SurrealDB issue
This issue commented a problem with file permissions when running the container. Here’s the excerpt:
The problem here is that the container is trying to create a file inside the /mydata
directory, but it doesn’t have the permissions to do so. To know exactly what the problem is, we would need to know the permissions of the /mydata
directory on the host, and the user used in the container.
Checking out the permissions of the /mydata
directory is easy
# run the container, expect it to fail as the user mentioned
# check the permissions of the /mydata directory
Here we can see that the /mydata
directory is owned by root
, and it has read, write, and execute permissions for the owner, and read and execute permissions for the group and execute for everyone else. This means that the container needs to run as root
in order to create files inside the /mydata
directory. Let’s test that out:
The container ran successfully as root!
The twist
You might be asking yourself Isn’t there an alternative to running the container as root?. The answer is yes! and we need to take a closer look at our /mydata
directory in our host machine. Do you remember creating it? No! That’s because the docker daemon created it for you when you ran docker run -v ...
. By default, docker will create the folder with root
user and group. We can change this by previously creating the folder with the correct user and group.
# if you are following along, you'll want to delete the folder generated previously
# Since / is owned by root, I suggest we use our current folder to store the data
# Check that the folder is owned by your current user
# run the container without root
# various logs
But wait, I got an error! That’s because the container is not running with the same user and group as the host. We can fix this by running the container with the same user and group as the host.
# happy logs :D
Going a bit deeper
If you’ve got experience with Docker, you might be thinking that it’s weird that you havent’ gotten to this problem earlier with other containers. There are 2 reasons for this:
- usually, containers run with
root
user, giving the container full access over your files - when the containers run with a different user, they usually run with the default user that has the same uid and gid as the host’s user (
1000:1000
in unix systems)
The problem in the surrealdb
image is that it doesn’t follow the second point. We can check what user and group the container is running with by running the id
command inside the container. We can do this by creating a new Dockerfile that copies the id
command from a busybox image and running it inside the container.
# file name: Dockerfile
FROM surrealdb/surrealdb:latest
# Install shell and utilities, like the `id` command
COPY --from=busybox:1.35.0-uclibc /bin/* /bin/
Then we build the image and run the id
command inside the container with -u
and -g
flags to get the user and group.
The surrealdb
container is running with user and group 65532:65532
. If we were to create another image and add USER 1000
to the Dockerfile, we wouldn’t need to specify the --user
flag when running the container (if we are using the default 1000:1000
user in the host).
Conclusion
In this post, we’ve learned about file permissions in Linux and Docker containers. We’ve seen how the file permissions are managed by the host’s kernel, and how the container’s user can affect the file permissions. We’ve also seen how to solve file permission issues by running the container with the correct user and group. I hope this post has been helpful, and that you now have a better understanding of file permissions in Docker containers