├── project ├── test.txt └── index.php ├── .gitignore ├── img └── ok.png ├── .env.dist ├── tests.sh ├── Dockerfile ├── .travis.yml ├── docker-compose.yml ├── php └── entrypoint.sh ├── nginx └── default.conf ├── LICENSE └── README.md /project/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea/ -------------------------------------------------------------------------------- /img/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypereirareis/docker-permissions/HEAD/img/ok.png -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | PHPUID=YOUR_LOCAL_USER_UID # often 1000 but not always 2 | PHPGID=YOUR_LOCAL_USER_GID # often 1000 but not always -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | URI='http://127.0.0.1:8888/' 4 | 5 | docker-compose rm -f || true 6 | docker-compose build 7 | docker-compose up -d 8 | sleep 3 9 | curl "${URI}" | grep -q "READ OK" && sleep 1 10 | curl "${URI}" | grep -q "WRITE OK" && sleep 1 11 | curl "${URI}" | grep -q "EXECUTION OK" && sleep 1 12 | 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2.3-fpm-alpine3.7 2 | 3 | RUN echo http://dl-2.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories 4 | RUN apk --no-cache add shadow 5 | 6 | ARG PROJECT_DIR_ARG='/usr/share/nginx/html' 7 | ENV PROJECT_DIR=$PROJECT_DIR_ARG 8 | 9 | RUN mkdir -p $PROJECT_DIR 10 | COPY ./project $PROJECT_DIR 11 | RUN chown -R www-data:www-data $PROJECT_DIR 12 | 13 | COPY ./php/entrypoint.sh /entrypoint.sh 14 | RUN chmod +x /entrypoint.sh 15 | 16 | WORKDIR $PROJECT_DIR 17 | VOLUME $PROJECT_DIR 18 | 19 | ENTRYPOINT ["/entrypoint.sh"] 20 | CMD ["php-fpm"] 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | env: 4 | global: 5 | - PHPUID=2000 6 | - PHPGID=2000 7 | - DEBUG=true 8 | 9 | before_install: 10 | - sudo rm /usr/local/bin/docker-compose 11 | - curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > docker-compose 12 | - chmod +x docker-compose 13 | - sudo mv docker-compose /usr/local/bin 14 | 15 | language: bash 16 | 17 | services: 18 | - docker 19 | 20 | script: 21 | - chmod +x tests.sh 22 | - bash tests.sh 23 | 24 | after_script: 25 | - docker-compose stop 26 | - docker-compose rm -f 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | php: 4 | build: 5 | context: . 6 | # args: 7 | # - PROJECT_DIR_ARG=/usr/share/nginx/public 8 | container_name: ypr-permissions-php 9 | environment: 10 | - PHP_UID=${PHPUID} 11 | - PHP_GID=${PHPGID} 12 | - DEBUG=false # true, to have more info from the entry point 13 | volumes: 14 | - ./project:/usr/share/nginx/html 15 | nginx: 16 | image: nginx:1.13.9-alpine 17 | container_name: ypr-permissions-nginx 18 | depends_on: 19 | - php 20 | volumes: 21 | - ./project:/usr/share/nginx/html 22 | - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro 23 | ports: 24 | - 8888:80 # http://127.0.0.1:8888/ -------------------------------------------------------------------------------- /php/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | [ "$DEBUG" = "true" ] && set -x 3 | set -e 4 | 5 | # -- 6 | PHP_UID_DEFAULT=$(id -u www-data) 7 | 8 | # Here we check if GID and UID are already defined properly or not 9 | # i.e Do we have a volume mounted and with a different uid/gid ? 10 | if [[ -z "$(ls -n $PROJECT_DIR | grep $PHP_UID_DEFAULT)" ]]; then 11 | 12 | : ${PHP_UID:=$(id -u www-data)} 13 | : ${PHP_GID:=$(id -g www-data)} 14 | 15 | export PHP_UID 16 | export PHP_GID 17 | 18 | if [ "$PHP_UID" != "0" ] && [ "$PHP_UID" != "$(id -u www-data)" ]; then 19 | echo "Need to change UID and GID." 20 | usermod -u $PHP_UID www-data 21 | groupmod -g $PHP_GID www-data 22 | chown -R www-data:www-data $PROJECT_DIR 23 | echo "UID and GID changed to $PHP_UID and $PHP_GID." 24 | fi 25 | else 26 | echo "UID and GUI are OK !" 27 | fi 28 | 29 | exec "$@" -------------------------------------------------------------------------------- /project/index.php: -------------------------------------------------------------------------------- 1 | READ OK
'; 4 | echo 'If you see this line, it means that you are able to read and execute PHP files from: '.__DIR__.''; 5 | echo '

'; 6 | 7 | if (is_writable(__DIR__)){ 8 | echo 'WRITE OK
'; 9 | echo 'If you see this line, it means that you CAN write files in:'.__DIR__; 10 | } else { 11 | echo 'WRITE : ERROR
'; 12 | echo "If you see this line, it means that you CANNOT create files in:".__DIR__; 13 | } 14 | 15 | echo '

'; 16 | 17 | if (is_executable(__DIR__.'/test.txt')){ 18 | echo 'EXECUTION OK
'; 19 | echo 'If you see this line, it means that you CAN execute files in:'.__DIR__; 20 | } else { 21 | echo 'EXECUTION : ERROR
'; 22 | echo "If you see this line, it means that you CANNOT execute files in:".__DIR__; 23 | } -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /usr/share/nginx/html; 4 | 5 | location / { 6 | # try to serve file directly, fallback to app.php 7 | try_files $uri /index.php$is_args$args; 8 | } 9 | 10 | # Deny all . files 11 | location ~ /\. { 12 | deny all; 13 | } 14 | 15 | location ~ ^/index\.php(/|$) { 16 | fastcgi_pass php:9000; 17 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 18 | include fastcgi_params; 19 | 20 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 21 | fastcgi_param DOCUMENT_ROOT $realpath_root; 22 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 23 | 24 | fastcgi_buffer_size 128k; 25 | fastcgi_buffers 256 16k; 26 | fastcgi_busy_buffers_size 256k; 27 | fastcgi_temp_file_write_size 256k; 28 | 29 | internal; 30 | } 31 | 32 | # return 404 for all other php files not matching the front controller 33 | # this prevents access to other php files you don't want to be accessible. 34 | location ~ \.php$ { 35 | return 404; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yannick Pereira-Reis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker volumes and permissions 2 | 3 | [![Build Status](https://travis-ci.org/ypereirareis/docker-permissions.svg?branch=master)](https://travis-ci.org/ypereirareis/docker-permissions) 4 | 5 | This repository shows you a way to deal with read/write/exec permissions 6 | and how to define user and group ids using volumes from the host, when running containers. 7 | 8 | Indeed, user uid and gid are often different in the container and on the host system. 9 | And it's possible to use the same docker configuration on different hosts (local, test, stating,...) 10 | and with possibly different users from a uid/gid point of view. 11 | 12 | So if you want to use volumes, you need to understand how to configure the permissions of the project/folder 13 | mapped into your containers. 14 | 15 | :whale: **A second way of doing things is available at:** [ypereirareis/docker-permissions-reborn](https://github.com/ypereirareis/docker-permissions-reborn) 16 | 17 | ## Docker images 18 | 19 | For this demo project we are relying on two simple docker alpine images: 20 | 21 | * [Nginx (nginx:1.13.9-alpine)](https://hub.docker.com/_/nginx/) 22 | * [PHP-FPM (php:7.2.3-fpm-alpine3.7)](https://hub.docker.com/_/php/) 23 | 24 | We are using the nginx image directly simply overriding the configuration file with our own 25 | to be able to use PHP-FPM running in the `php` container. 26 | 27 | ```bash 28 | location ~ ^/index\.php(/|$) { 29 | fastcgi_pass php:9000; 30 | ... 31 | internal; 32 | } 33 | ``` 34 | 35 | For the PHP-FPM image we are building our own form the `php:7.2.3-fpm-alpine3.7` because we need to 36 | add a custom entry point to deal with permissions. 37 | 38 | ## The problem 39 | 40 | We have a problem if we use a volume to share our code from host to container. 41 | 42 | * The user running `php-fpm` in the container is `wwww-data` with `uid=100` and `gid=101`. 43 | * Our host user often has `uid=1000` and `gid=1000` but not always. 44 | * The `nginx` user will not be able to read/write/exec files from the volume if permissions are not defined properly. 45 | 46 | But I do not recommend to change permissions with `chmod` directly. 47 | The way I recommend is to change the owner of the shared directory to map uid and gid of the container user 48 | to the host user. 49 | 50 | ## The Dockerfile and entry point to change uig/gid 51 | 52 | The interesting part of the Dockerfile is this one: 53 | 54 | ```bash 55 | ARG PROJECT_DIR_ARG='/usr/share/nginx/html' 56 | ENV PROJECT_DIR=$PROJECT_DIR_ARG 57 | 58 | RUN mkdir -p $PROJECT_DIR 59 | COPY ./project $PROJECT_DIR 60 | RUN chown -R www-data:www-data $PROJECT_DIR 61 | ``` 62 | 63 | * In the entry point we are checking if uid and gid of the container user `nginx` must be changed. 64 | 65 | ```bash 66 | # -- 67 | PHP_UID_DEFAULT=$(id -u www-data) 68 | 69 | # Here we check if GID and UID are already defined properly or not 70 | # i.e Do we have a volume mounted and with a different uid/gid ? 71 | if [[ -z "$(ls -n $PROJECT_DIR | grep $PHP_UID_DEFAULT)" ]]; then 72 | 73 | : ${PHP_UID:=$(id -u www-data)} 74 | : ${PHP_GID:=$(id -g www-data)} 75 | 76 | export PHP_UID 77 | export PHP_GID 78 | 79 | if [ "$PHP_UID" != "0" ] && [ "$PHP_UID" != "$(id -u www-data)" ]; then 80 | echo "Need to change UID and GID." 81 | usermod -u $PHP_UID www-data 82 | groupmod -g $PHP_GID www-data 83 | chown -R www-data:www-data $PROJECT_DIR 84 | echo "UID and GID changed to $PHP_UID and $PHP_GID." 85 | fi 86 | else 87 | echo "UID and GUI are OK !" 88 | fi 89 | ``` 90 | 91 | * The possible new values are coming from environment variables. 92 | 93 | ```yaml 94 | php: 95 | build: 96 | context: . 97 | environment: 98 | - PHP_UID=${PHPUID} 99 | - PHP_GID=${PHPGID} 100 | ``` 101 | 102 | * We can define per-host custom UID/GID environment varaibles with a `.env` file. 103 | 104 | ```bash 105 | PHPUID=1000 106 | PHPGID=1000 107 | ``` 108 | 109 | # Run the demo 110 | 111 | ```bash 112 | $ git clone git@github.com:ypereirareis/docker-permissions.git && cd docker-permissions 113 | ``` 114 | 115 | * Copy `.env.dist` to `.env` and set your uid and gid values. 116 | * Comment/Uncomment volume from `docker-compose.yml` 117 | 118 | ```yaml 119 | services: 120 | php: 121 | volumes: 122 | - ./project:/usr/share/nginx/html 123 | ``` 124 | 125 | ```bash 126 | $ docker-compose build 127 | 128 | ``` 129 | 130 | ```bash 131 | $ docker-compose up 132 | Starting ypr-permissions-php ... 133 | Starting ypr-permissions-php ... done 134 | Recreating ypr-permissions-nginx ... 135 | Recreating ypr-permissions-nginx ... done 136 | Attaching to ypr-permissions-php, ypr-permissions-nginx 137 | ypr-permissions-php | UID and GUI are OK ! 138 | ypr-permissions-php | [07-Mar-2018 16:35:37] NOTICE: fpm is running, pid 1 139 | ypr-permissions-php | [07-Mar-2018 16:35:37] NOTICE: ready to handle connections 140 | ``` 141 | 142 | Go to [http://127.0.0.1:8888/](http://127.0.0.1:8888/) 143 | 144 | If everything is ok, you should see: 145 | 146 | ![OK result](./img/ok.png) 147 | 148 | # Tests 149 | 150 | ```bash 151 | chmod +x tests.sh && ./tests.sh 152 | ``` 153 | 154 | # LICENSE 155 | 156 | MIT License 157 | 158 | Copyright (c) 2018 Yannick Pereira-Reis 159 | 160 | Permission is hereby granted, free of charge, to any person obtaining a copy 161 | of this software and associated documentation files (the "Software"), to deal 162 | in the Software without restriction, including without limitation the rights 163 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 164 | copies of the Software, and to permit persons to whom the Software is 165 | furnished to do so, subject to the following conditions: 166 | 167 | The above copyright notice and this permission notice shall be included in all 168 | copies or substantial portions of the Software. 169 | 170 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 171 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 172 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 173 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 174 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 175 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 176 | SOFTWARE. 177 | --------------------------------------------------------------------------------