Magento, Docker & CaptainHook
We are big fans of Mark Shust's Docker Configuration for Magento project. We use a lot of our projects, the ones we start from scratch as well for ones that we have taken over and have some weird setup in place that we simply cannot get to work.
In contrast to Mark's setup, we do link all files and folders from the host into the container, performance-wise this is fine for us. More or less this means we expose the following volumes for the nginx and phpfpm container:
volumes: &appvolumes
- ./.git:/var/www/.git
- ~/.composer:/var/www/.composer:delegated
- ./src:/var/www/html
- ./docker/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf
- sockdata:/sock
Besides mounting the whole src folder - which contains the Magento project - we mount the .git folder as well as $HOME/.composer where we usually keep global authentication information, e.g. access credentials for repo.magento.com or our own Statis instance.
Since I am a big fan and contributor to CaptainHook - a tool to help you manage the git hooks of a project, I decided to install CaptainHook for a new Magento project we recently started. The plan was to go all-in on docker and to not rely on any host installs at all. CaptainHook supports Docker for a while now and a few problems have been fixed since then. I was hoping for a smooth ride but I ran into quite a few issues of this particular setup.
On my host machine, the files & folder structure looks like this, the src folder gets mounted to /var/www/html in the container.
|-.git
|-bin
|-docker
|-src
|-app
|---code
|---design
|-composer.json
|-composer.lock
|-captainhook.json
Since git commits run on the host but CaptainHook should run in the container we need a shell script on the host that acts as an entrypoint for all git hooks. The script will redirect the call to CaptainHook running in the container. The CaptainHook configuration (captainhook.json) looks like this:
{
"config": {
"run-mode": "docker",
"run-exec": "./bin/githook",
"git-directory": "/var/www/.git/",
"run-path": "/var/www/html/vendor/bin/captainhook"
}
}
This will make sure CaptainHook knows that it is running inside a container, that the ./bin/githook script is the entrypoint for all git hooks. Besides that, it was important to let CaptainHook know where the git metadata resides inside the container. When I tried this for the first time I did not realize that I need to mount the .git directory inside the container and CaptainHook failed to run. Since it helps to have a similar folder layout I mounted the .git directory to /var/www/.git/ so that structure-wise the host & container looks the same. However, there's a slight difference between host and container that causes some trouble. On the host, the files reside in a src folder in the container the directory is called html. But let's ignore that for now and look at the ./bin/githook script instead:
#!/bin/bash
[ -z "$1" ] && echo "Please specify a CLI command (ex. ls)" && exit
# make sure phpfpm container is running before executing the git hook
docker-compose up --no-recreate -d phpfpm
docker-compose exec -w /var/www -T phpfpm "$@"
Two important this happen here: First, we make sure the phpfpm container is really running. Second, we set the work directory to /var/www, by default it would be /var/www/html which would completely confuse CaptainHook as to where look for the .git folder and the respective source files.
Make sure to have the captainhook.json configuration file as well as the ./bin/githook file in place before trying to install CaptainHook via Composer. If not, CaptainHook tries to install the git hooks but will fail due to the exotic setup.
When adding new hooks to the captainhook.json configuration, you have to keep a few things in mind. This, for example, is how our pre-commit hooks look like:
"pre-commit": {
"enabled": true,
"actions": [{
"action": "html/vendor/bin/phpcs --standard=Magento2 html/app/code/",
"options": []
},{
"action": "html/vendor/bin/phpunit -c /var/www/html/dev/tests/unit/
phpunit.xml.dist /var/www/html/app/code/",
"options": []
}]
}
Since we set the workdir in ./bin/githook to /var/www the action command needs to have the path specified from this location, meaning we need to prefix the commands with html/ - This feels a bit odd at first but makes perfect sense.
Executing 3rd party tools in this setup works absolutely fine. The problem is with CaptainHook actions that operate directly on the Git history. Due to the slight path difference between the host and the container, there's a mismatch that CaptainHook can't cope with. This took me quite a while to realize. We did not yet fix this in our set-up since we only rely on 3rd party tools. As far as I can see you have 2 options to fix this:
- Create a symlink from /var/www/html to /var/www/src
- Mount the src folder from the host as src container in the container and reconfigure nginx to be aware of that
Other than that, I am quite happy with the setup. It feels a bit slower to run CaptainHook this way as the ./bin/githook script tries to spin up the container first before running CaptainHook, but it is still fast enough to not be annoying. As you can see CaptainHook is extremely flexible and works quite well in "weird setups" like this.