Below I describe how I use Docker Compose for my PHP projects, although most of the concepts are helpful for any technology that you use. I will use Magento as an example, but there’s near-zero Magetno-specific stuff and this will work for any framework that you use.
I don’t pretend to be the author of all the techniques described here, I give all the thanks and credits to people that actually invented this.
Folder structure
This is how I organize files in the repo:
1 | . |
The docker
folder contains all docker-related files except for Docker Compose YAMLs.
I put all files for local (development) purpose into a local
sub-folder. This is becasue you might want to keep different Dockerfiles and configs for diferent environments in your repository as well.
Say, you have a “Testing” environment. Then:
- put Dockerfiles and configs to
docker/testing
folder. - create
docker-compose.testing.yaml
in the root folder. - bring your containers up on the testing environemnt by running:
docker-compose -f docker-compose.testing.yaml up --build -d
As local environment is used most often, I prefer local YAML files not to have “.local” suffix, so that I don’t need to specify the list of files each time for docker-compose
commands.
docker-compose.yaml
The contents should be self-explanatory. The only trick here is using YAML alias (codebase
) not to repeat ourselves with mounting application volume to both nginx and php-fpm containers.
1 | version: '3' |
As you can see I also prefer to name my services in a more abstract way than just “php” or “nginx”. I like it more because I can change the underlying technology later without much pain (i.e. mariadb to mysql).
I also include the app name in the service name so that it is possible to run different, let’s say, php containers in the same network.
Using override file
Note that I don’t expose or map any ports in docker-compose.yaml
above. I find this important because another developer can have ports that I use already taken on his machine. To manage this I use docker-compose.override.yaml
which is loaded by default if present. Find out more about it at https://docs.docker.com/compose/extends/.
I keep this file ignorred by GIT and instead version docker-compose.override.example.yaml
file, so that it is easy for everyone to start.
docker-compose.override.yaml
1 | version: '3' |
Here I map nginx container port 80 to local port 8080 so I can open my website in browser: http://magento.localhost:8080.
I also map mariadb container port 3306 to local port 3326 so that I can connect to the database from my local machine: mysql -h 127.0.0.1 -P 3326 -u root -p magento
.
User permissions trick
By default docker containers are running from the root user. Sometimes they use custom user, like “nginx” for example. So files that are created in containers might appear owned by root or even unknown user in mounted folders on your local machine.
To overcome this inconvenience I use the following trick and change the user IDs inside containers to my local user.
- In your Dockerfile define the
USER_ID
andGROUP_ID
args and modify the user from which the container is running:
For example for nginx we’ll have:
1 | FROM nginx:1.15.2 |
For php-fpm:
1 | FROM php:7.2-fpm |
- In your
docker-compose.override.yaml
define the actual user and group IDs that you want to use:
1 | version: '3' |
You can find out your current local user and group IDs with id -u
and id -u
respectively. Normally in Ubuntu these are both 1000.
Working with Composer
I only work with composer locally, never run it inside the container. This allows me not to care about copying ssh keys, composer repository credentials and other stuff.
As a disadvantage this leads to necessity of having PHP and Composer installed locally, but I always do have them latest anyway. However it is important to specify the PHP version that you use on the project for composer, so that it installs correct packages. You can do it like this in composer.json
:
1 | { |
You can also specify PHP extensions versions here, see platform section documentation for more capabilities.
With properly configured platform in composer.json
you can avoid adding “–ignore-platform-reqs” to your composer install or update command.
Bin helper
All modern frameworks have CLI interface for development purposes and it is being used quite often. Normally you must do this inside the container for it to work properly, but that’s not very convenient. The elegant way to improve this is to create a “bin helper” as I call it, which will pass the command you want to the container you need.
Below is the example for Magento, you can modify it for the framework of your choice:
bin/magento
1 |
|
So now I just run this as if it would be the actual bin/magento
file:
1 | bin/magento cache:flush |
That’s it for now.
Check out related topics: