How to manage docker-compose Symfony project using Make
Tags: symfony, php, docker, make
Published: 2022-06-18 19:19:21

In my [previous post](https://dev.to/gricob/how-to-setup-docker-compose-for-symfony-projects-47kn) I explained how to setup docker-compose for a symfony project. Manage docker-compose projects can be anoying if we use docker/docker-compose commands for it. With Make, we can manage it in a simpler way.

## What is Make?
According to the make page from [GNU project website](https://www.gnu.org/software/make/):

> GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.

> Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.

A good place to learn how to use Makefiles is [makefiletutorial website](https://makefiletutorial.com/#getting-started)

## How would we use Make?
As you can see in its definition, it's main pourpose is to generate files from source code, but we will use it to simplificate execution of the commands needed to manage a docker-compose symfony project. 

Make's targets allows us to create aliases to other commands. For example, to run a symfony command in your container you will run something like:

`docker exec -it bin/console your:command`

If you need to run this command often, this could become anoying. With make, we can define the following target:

```
console/%
    docker exec -it bin/console $*
```

To use this target you simply run 

`make console/your:comand`

That is pretty much simpler than docker's full command.


## Creating our Makefile
Firs of all, we create a file name `Makefile` in our project root directory.

### Docker related targets
The first target we are going to create is to execute docker-compose commands:

```makefile
docker-compose/%:
	@USER=${USER} docker-compose $*
```
`USER=${USER}` is to tell docker-compose which user we want to use inside the container. This is needed to prevent permissions errors between host and container generated files. 
To make it work, we will add the following variable at the begining of our Makefile:

```makefile
USER := $(shell id -u):$(shell id -g)
```

With this, we are setting the `user:group` of our user to the `USER` variable.

Finally, we need to add `user` option to our service configuration (in docker-composer.yml) and set it to the environment variable `$USER`:

```
example_php:
    container_name: example_php
    user: $USER
    build:
      context: .
    working_dir: /var/www/app/
    volumes:
      - './:/var/www/app/'
```

#### Helper targets

To keep things simple on our daily usage, we will add the following helper targets:

- up: Build and start services
- start: Start services
- stop: Stop services
- build: Build or rebuild services
- exec: Execute commands inside PHP service
- bash: Execute bash inside our PHP service

```makefile
up:
	@make docker-compose/"up -d"

start:
	@make docker-compose/"start"

stop:
	@make docker-compose/"stop"

build:
	@make docker-compose/build

exec/%:
	@docker exec -it ${PHP_CONTAINER} $*

bash:
	@make exec/bash
```

### Other targets
Given the previously defined docker targets, it's easy to add targets. For example, we can add a symfony console target to run console commands:

```makefile
console/%:
	@make exec/"bin/console $*"
```

As you can see, we use the `exec` target previously defined.
We can add all targets that we could need using this format.

### All together

```makefile
# ----------------------------------------------------------------------------
# Configuration
# ----------------------------------------------------------------------------

USER := $(shell id -u):$(shell id -g)
NGINX_CONTAINER = "example_nginx"
PHP_CONTAINER = "example_php"

# ----------------------------------------------------------------------------
# Docker
# ----------------------------------------------------------------------------

docker-compose/%:
	@USER=${USER} docker-compose $*

up:
	@make docker-compose/"up -d"

build:
	@make docker-compose/build

exec/%:
	@docker exec -it ${PHP_CONTAINER} $*

bash:
	@make exec/bash

# ----------------------------------------------------------------------------
# Composer
# ----------------------------------------------------------------------------

composer/%:
	@make exec/"composer $*"

composer-install:
	@make composer/"install"

composer-require/%:
	@make composer/"require $*"

composer-require-dev/%:
	@make composer/"require --dev $*"

# ----------------------------------------------------------------------------
# Symfony
# ----------------------------------------------------------------------------

console/%:
	@make exec/"bin/console $*"

# ----------------------------------------------------------------------------
# Tests
# ----------------------------------------------------------------------------

phpunit/%:
	@make exec/"vendor/bin/phpunit -c phpunit.xml.dist $*"

test:
	@make phpunit/" "

test/%:
	@make phpunit/"--testsuite $*"
```

## How to use it
### Set up
If you don't have built your services yet, run `make up`.
If services are already built, run `make up`

### Dependencies
Run `make composer-install` to install all dependencies.

Run `make composer-require/example/package` to require packages. For example, to install ramsey/uuid, you will run `make composer-require/ramsey/uuid`.

### Symfony commands
You can run any symfony command running `make console/your:command`. For example, if you need to clear cache, you will run `make console/cache:clear`. You can pass arguments and options wrapping command into double quotes: `make console/cache:clear -e prod`.

### Tests
To run all suites, run `make test`. 
If you want to run only your unit test suite, run `make test/unit`