Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

DoeJohn's avatar

Docker Compose: Understanding services for PHP & Composer

Hi,

I started learning about Docker and setting up PHP & Composer. I looked at some examples and saw the following example: https://github.com/aschmelyun/docker-compose-laravel/blob/main/docker-compose.yml

In docker-compose.yml, among others, there are two services: for php and for composer:

php:
    build:
      context: ./dockerfiles
      dockerfile: php.dockerfile
      args:
        - UID=${UID:-1000}
        - GID=${GID:-1000}
    ports:
      - "9000:9000"
    volumes:
      - ./src:/var/www/html:delegated
    networks:
      - laravel

  composer:
    build:
      context: ./dockerfiles
      dockerfile: php.dockerfile
      args:
        - UID=${UID:-1000}
        - GID=${GID:-1000}
    volumes:
      - ./src:/var/www/html
    depends_on:
      - php
    entrypoint: [ 'composer', '--ignore-platform-reqs' ]
    networks:
      - laravel

and both are specified to use the same php.dockerfile:

build:
    context: ./dockerfiles
    dockerfile: php.dockerfile

Finally, php.dockerfile looks like this (I left out some parts so it wouldn't be too long here, you can see the full version here https://github.com/aschmelyun/docker-compose-laravel/blob/main/dockerfiles/php.dockerfile):

FROM php:8-fpm-alpine

ARG UID
ARG GID

ENV UID=${UID}
ENV GID=${GID}

RUN mkdir -p /var/www/html

WORKDIR /var/www/html

COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

# MacOS staff group's gid is 20, so is the dialout group in alpine linux. We're not using it, let's just remove it.
RUN delgroup dialout

RUN addgroup -g ${GID} --system laravel
RUN adduser -G laravel --system -D -s /bin/sh -u ${UID} laravel

RUN sed -i "s/user = www-data/user = laravel/g" /usr/local/etc/php-fpm.d/www.conf
RUN sed -i "s/group = www-data/group = laravel/g" /usr/local/etc/php-fpm.d/www.conf

RUN docker-php-ext-install pdo pdo_mysql
    
USER laravel

CMD ["php-fpm", "-y", "/usr/local/etc/php-fpm.conf", "-R"]

4 (interrelated) questions:

Question 1: Since in docker-compose.yml both services, php and composer, use the same php.dockerfile, does this mean the following: Only one image will be build (for php service, as it comes before composer service in docker-compose.yml file), and from that same image two separate containers will be created: one for php service and another for composer service, right?

Question 2: If the answer to Question 1 is yes (if separate containers will be created) - does that make any sense because those two containers will be identical? Also, In php.dockerfile there is:

COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

This means that the "php" container will already have Composer in it, so I don't see the point of having a separate identical container for "composer"?

Question 3: The following is specified in docker-compose.yml for the composer service:

entrypoint: [ 'composer', '--ignore-platform-reqs' ]

If I understand correctly, this means that every time the “composer” container is started, the composer --ignore-platform-reqs will be executed, right? But that doesn’t make sense because composer --ignore-platform-reqs will do nothing as there is no any command specified there: composer COMMAND --ignore-platform-reqs (for example, composer install –ignore-platform-reqs).

Question 4: Finally, the last question is about the php.dockerfile, there are a few things that I don't think are needed and don't do anything, but maybe I'm wrong (please correct me if I’m wrong):

  • WORKDIR /var/www/html - I don't see the purpose of this in that particular php.dockerfile;
  • RUN delgroup dialout - same goes for this; even if we keep that dialout group, I don't see how it can create any problem?
  • USER laravel - this is applied at the very end, so I also don't see the purpose of this (without that it should work the same, without any problems)?
0 likes
2 replies
aschmelyun's avatar
Level 4

Hi @doejohn

I'm the author of that GitHub repo, so I thought I'd help you out and answer your questions here, in the order you asked them:

  1. You are correct, only one image will be built since both of those services use the same image (php.dockerfile). This container image will be used to spin up two separate containers when you run docker compose up.

  2. You are correct again, both containers will contain the composer library. So, why have a separate instance for composer? The reason is separation of concerns. The PHP container is designed to spin up and stay running, running the php-fpm process in the background. However, the composer container is designed to be spun up and only run briefly to run composer commands (e.g. installing packages). This goes with the Docker way of thinking, which is that only one service should be performing an action at the same time. We could have the PHP container running composer commands by using docker compose exec php composer require ... but it makes more sense to separate it out as its own service.

  3. Correct, on spin-up without a command the container will just end with no real output. This is because this particular service is meant to be used with docker compose run. Example: docker compose run composer update. The entrypoint provides the docker container additional arguments after the container name. So in this case, the update we used in that command gets tacked on after composer --ignore-platform-reqs.

  4. The WORKDIR line sets the current working directory to where our code lies, so when composer commands are ran, they take place at the project root. The dialout group is removed because of some MacOS issue I ran into in the past (that I don't remember), and the laravel user is added in because the main PHP config file uses it instead of the default www user due to some weird permissions issues.

I hope these are all succinct answers and if you have any more questions, I'd be happy to help out further!

2 likes
DoeJohn's avatar

@aschmelyun Thank you very much!

  1. You are correct, only one image will be built since both of those services use the same image (php.dockerfile). This container image will be used to spin up two separate containers when you run docker compose up.

I just pulled the docker-compose-laravel project on my local, and after up --build I run docker-compose images and it shows that there are two images built:

but I guess that doesn't change/affect anything (it will just take a bit of memory on disk), but the end result (goal) is important, and that is what you stated in the answer to the second question:

  1. ... the composer container is designed to be spun up and only run briefly to run composer commands (e.g. installing packages). This goes with the Docker way of thinking, which is that only one service should be performing an action at the same time. We could have the PHP container running composer commands by using docker compose exec php composer require ... but it makes more sense to separate it out as its own service.

Also, if I understand correctly, the container for Composer will still be different than the container for PHP: Since in docker-compose.yml for "composer" service there is:

entrypoint: [ 'composer', '--ignore-platform-reqs' ]

this means that when starting the container for Composer, the command listed at the end of php.dockerfile will not be executed:

CMD ["php-fpm", "-y", "/usr/local/etc/php-fpm.conf", "-R"]

instead, composer --ignore-platform-reqs will be executed and "php-fpm" will never be started in that container.

  1. ... the laravel user is added in because the main PHP config file uses it instead of the default www user due to some weird permissions issues.

This is something that confused me because we, in the php.dockerfile, have already created the "laravel" user and group, and we have updated the www.conf so that the PHP-FPM process runs as "laravel" instead of "www-data" :

RUN addgroup -g ${GID} --system laravel
RUN adduser -G laravel --system -D -s /bin/sh -u ${UID} laravel

RUN sed -i "s/user = www-data/user = laravel/g" /usr/local/etc/php-fpm.d/www.conf
RUN sed -i "s/group = www-data/group = laravel/g" /usr/local/etc/php-fpm.d/www.conf

So I was wondering why we also have USER laravel near the end of php.dockerfile ... But now I think I understand why (please correct me if I'm wrong): There are two reasons:

The first reason is because Composer, by default, runs as root (and with USER laravel we change that: every time we run docker compose exec composer ... Composer will run as "laravel" and therefore we will not have the problems with permissions that you described in the blog post).

Another reason is (I'm not sure if I've got this right): Yes, we created "laravel" user and group in the php.dockerfile, and updated the www.conf file to use it instead of "www-data", but this only applies to the "children" of the PHP-FPM process ("child PHP-FPM processes"), but the main "parent" PHP-FPM that we start with:

CMD ["php-fpm", "-y", "/usr/local/etc/php-fpm.conf", "-R"]

would run as "root" if we didn't have USER laravel specified (and that could also cause issues with permissions I guess).

Once again, thank you very much for your help! :)

Please or to participate in this conversation.