9
\$\begingroup\$

I've created a repo for some Docker containers that work together with docker-compose to make a very easy and quick installation for Laravel including nginx, mariadb, and redis. Laravel is known for having one somewhat annoying step to get it started, which is that the storage and bootstrap/cache directories have to be writable by the webserver/php process:

chmod -R 777 storage bootstrap/cache

However, docker containers that use shared volumes between the container and host tend to have permission issues because of discrepancies between uids and guids differing between the container and the host.

This is a Mac-only project until I get these basic issues solved. The repository can be found here.

I would greatly appreciate solutions for how to have the PHP process user in the PHP container have write permissions on those directories in the shared volume.

This install script (called by typing ./install into the terminal) is what should be theoretically setting the permissions:

install

#!/bin/bash
echo "installing and building docker containers..."
docker-compose up -d
echo "installing laravel/installer via composer..."
rm -rf code
if [[ $(composer global show) != *laravel/installer* ]]
 then
 composer global require "laravel/installer"
fi
echo "installing laravel..."
laravel new code
echo "changing working directory to code..."
cd code
echo "setting permissions..."
chmod -R 777 storage bootstrap/cache
docker exec ${PWD##*/}_php_1 chgrp -R www-data /code
docker exec ${PWD##*/}_php_1 chmod -R 777 storage bootstrap/cache
echo "installing predis..."
composer require "predis/predis"
echo "installing correct database settings to laravel..."
sed -i '' "s/DB_HOST=127.0.0.1/DB_HOST=mariadb/" .env
sed -i '' "s/DB_DATABASE=homestead/DB_DATABASE=laravel/" .env
sed -i '' "s/DB_USERNAME=homestead/DB_USERNAME=laravel/" .env
sed -i '' "s/REDIS_HOST=127.0.0.1/REDIS_HOST=redis/" .env
sed -i '' "s/CACHE_DRIVER=file/CACHE_DRIVER=redis/" .env
sed -i '' "s/SESSION_DRIVER=file/SESSION_DRIVER=redis/" .env
echo "returning working directory to previous state..."
cd ..
echo "installation complete"

docker-compose.yml

version: '2'
services:
 web:
 image: nginx:alpine
 ports:
 - "80:80"
 volumes:
 - ./code:/code
 - ./site.conf:/etc/nginx/conf.d/site.conf
 networks:
 - front-tier
 - back-tier
 php:
 image: laravel_pdo_php
 build: ./php
 working_dir: /code
 volumes:
 - ./code:/code
 - ./php.ini:/usr/local/etc/php/php.ini
 networks:
 - back-tier
 expose:
 - "9000"
 mariadb:
 image: mariadb:latest
 ports:
 - "3306:3306"
 volumes:
 - mariadb:/var/lib/mysql
 networks:
 - back-tier
 environment:
 MYSQL_ROOT_PASSWORD: root
 MYSQL_DATABASE: laravel
 MYSQL_USER: laravel
 MYSQL_PASSWORD: secret
 expose:
 - "3306"
 redis:
 image: redis:alpine
 networks:
 - back-tier
 expose:
 - "6379"
volumes:
 mariadb:
 driver: local
networks:
 front-tier:
 driver: bridge
 back-tier:
 driver: bridge

php/Dockerfile

FROM php:7-fpm
RUN docker-php-ext-install pdo pdo_mysql
CMD ["php-fpm"]
Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Aug 19, 2016 at 14:47
\$\endgroup\$
0

2 Answers 2

2
\$\begingroup\$

If any of the commands fail, then the script ploughs blindly on afterwards. That's probably not what we want - and it's certainly not what we want if this command fails:

cd code

We can fix this by testing the exit status of the command, e.g. with ||, but I recommend setting the -e flag of the shell to make failed commands exit the shell immediately. While we're at it, let's set -u so that misspelling a variable name is caught as an error:

set -eu

We can simplify this if/then:

if [[ $(composer global show) != *laravel/installer* ]]
 then
 composer global require "laravel/installer"
fi

If we read it as "either laravel/installer is already required, else require it", that becomes

[[ $(composer global show) == *laravel/installer* ]] ||
 composer global require "laravel/installer"

Or we could write it in portable shell, allowing us to use plain /bin/sh rather than needing Bash:

case "$(composer global show)" in
 *laravel/installer*) ;; # no action needed
 *) composer global require "laravel/installer" ;;
esac

Here, we expand a variable unsafely:

docker exec ${PWD##*/}_php_1 chgrp -R www-data /code
docker exec ${PWD##*/}_php_1 chmod -R 777 storage bootstrap/cache

We need quotes, i.e. "${PWD##*/}_php_1". And we might want to consider running both commands together:

docker exec "${PWD##*/}_php_1" \
 sh -c 'chgrp -R www-data /code && chmod -R 777 storage bootstrap/cache'

Even in a Docker container, I feel dirty about granting write permission to "other" users (i.e. the 002 bit).


As mentioned by Sᴀᴍ Onᴇᴌᴀ, the multiple sed commands modifying one file can be combined. Also, if we use the keys to address the relevant lines, we can avoid repetition and be more robust about initial values:

sed -i '' \
 -e '/^DB_HOST=/s/=.*/=mariadb/' \
 -e '/^DB_DATABASE=/s/=.*/=laravel/' \
 -e '/^DB_USERNAME=/s/=.*/=laravel/' \
 -e '/^REDIS_HOST=/s/=.*/=redis/' \
 -e '/^CACHE_DRIVER=/s/=.*/=redis/' \
 -e '/^SESSION_DRIVER=/s/=.*/=redis/' \
 .env

This is still a little tedious to write, so I have previously written a function to create a suitable sed script from a list of keys and values (which as written needs Bash for the ${//} substitution; we don't actually need that here because none of our values contain /, so we could get away with "$@" instead of "${@//\//\\/}"):

change_values() {
 printf '/^%s *=/s/=.*/= %s/\n' "${@//\//\\/}"
}
sed -i '' \
 -e "$(change_values \
 DB_HOST mariadb \
 DB_DATABASE laravel \
 DB_USERNAME laravel \
 REDIS_HOST redis \
 CACHE_DRIVER redis \
 SESSION_DRIVER redis \
 )" \
 .env

This command is pointless:

echo "returning working directory to previous state..."
cd ..

The only thing we do in this process after that is a simple echo, which is unaffected by the change of directory. We can simply remove these two lines.

answered Sep 22, 2022 at 5:57
\$\endgroup\$
0
1
\$\begingroup\$

Permissions issue

I would greatly appreciate solutions for how to have the PHP process user in the PHP container have write permissions on those directories in the shared volume.

Instead of having a docker container for nginx, my team switched to just using the built-in PHP webserver, initiated with the php artisan serve command - which was mentioned in the Laravel 7 documentation:

Local Development Server

If you have PHP installed locally and you would like to use PHP's built-in development server to serve your application, you may use the serve Artisan command. This command will start a development server at http://localhost:8000:

php artisan serve

More robust local development options are available via Homestead and Valet.

Subsequent versions of the documentation appear to mention this command in the Your First Laravel Project section, though they also mention using Sail in the section Laravel & Docker.

When using the local development server there is no need to worry about permissions on the shared directories.

MY IDE, i.e. PHPStorm, allows for Startup tasks to be configured, and I have a task that runs php artisan serve --port=8000 so I don't have to manually run it.

Install script

Rimraf

The sixth line is:

rm -rf code

Perhaps this is just added to run the script subsequent times, though I would be wary of a script that force removed a directory without prompting me first. it may be wise to check to see if that directory exists before removing it. If it does exist, then perhaps it would be wise to warn the user, possibly even requiring confirmation before removal.

Repeated sed calls

The shell script updates the env file using sed:

echo "installing correct database settings to laravel..."
sed -i '' "s/DB_HOST=127.0.0.1/DB_HOST=mariadb/" .env
sed -i '' "s/DB_DATABASE=homestead/DB_DATABASE=laravel/" .env
sed -i '' "s/DB_USERNAME=homestead/DB_USERNAME=laravel/" .env
sed -i '' "s/REDIS_HOST=127.0.0.1/REDIS_HOST=redis/" .env
sed -i '' "s/CACHE_DRIVER=file/CACHE_DRIVER=redis/" .env
sed -i '' "s/SESSION_DRIVER=file/SESSION_DRIVER=redis/" .env

As this answer explains the multiple sed commands can be combined using the -e flag, allowing for reduction of overwriting of files.

sed -i '' -e "s/DB_HOST=127.0.0.1/DB_HOST=mariadb/" \
 -e "s/DB_DATABASE=homestead/DB_DATABASE=laravel/" \
 -e "s/DB_USERNAME=homestead/DB_USERNAME=laravel/" \
 -e "s/REDIS_HOST=127.0.0.1/REDIS_HOST=redis/" \
 -e "s/CACHE_DRIVER=file/CACHE_DRIVER=redis/" \
 -e "s/SESSION_DRIVER=file/SESSION_DRIVER=redis/" .env
answered Sep 21, 2022 at 22:53
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.