61

I'm trying to Dockerize my laravel app. The app is already built and in git, but I .gitignore my vendor folder. I've added a Dockerfile, which looks like this:

FROM php:7.1-fpm-alpine

RUN apk update && apk add curl && \
  curl -sS https://getcomposer.org/installer | php \
  && chmod +x composer.phar && mv composer.phar /usr/local/bin/composer

RUN apk --no-cache add --virtual .build-deps $PHPIZE_DEPS \
  && apk --no-cache add --virtual .ext-deps libmcrypt-dev freetype-dev \
  libjpeg-turbo-dev libpng-dev libxml2-dev msmtp bash openssl-dev pkgconfig \
  && docker-php-source extract \
  && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ \
                                   --with-png-dir=/usr/include/ \
                                   --with-jpeg-dir=/usr/include/ \
  && docker-php-ext-install gd mcrypt mysqli pdo pdo_mysql zip opcache \
  && pecl install mongodb redis xdebug \
  && docker-php-ext-enable mongodb \
  && docker-php-ext-enable redis \
  && docker-php-ext-enable xdebug \
  && docker-php-source delete \
  && apk del .build-deps

WORKDIR /var/www/html

COPY composer.json composer.lock ./
RUN composer install --no-scripts --no-autoloader

COPY . .
RUN chmod +x artisan

RUN composer dump-autoload --optimize && composer run-script post-install-cmd

CMD php artisan serve --host 0.0.0.0 --port 5001

When I build, this seems to work great. I see the dependencies getting downloaded, I see the autoload file being generated in the output. However, once the build is complete, the vendor folder is not actually there. I'm guessing it was all done in an intermediate container which was then removed? So when I run docker-compose up, I get: Fatal error: require(): Failed opening required '/var/www/html/bootstrap/../vendor/autoload.php'

This thread seems to point to the issue - possibly - but doesn't really provide a solution: Composer install doesn't install packages when running in Dockerfile

JBxOnline
  • 1,339
  • 1
  • 9
  • 10
  • Try to use COPY composer.json ./ instead of COPY composer.json composer.lock ./ (don't copy composer.lock) And have this line COPY . . above composer install – Iurii Drozdov Oct 17 '17 at 09:20
  • 3
    COPY . . will copy the whole directory, so that would copy composer.json and composer.lock anyway. And why would I not want to copy the lock file? This is what I want to base my install on. – JBxOnline Oct 17 '17 at 09:26
  • 1
    You should add composer.lock to your .dockerignore then. Please take a look: https://getcomposer.org/doc/01-basic-usage.md#installing-with-composer-lock – Iurii Drozdov Oct 17 '17 at 09:30
  • 5
    If I do that, then I'm essentially running composer update. This is not what I want. I'm not after the latest versions of the dependencies, I want the versions that are in the composer.lock file. Currently, the vendor folder is not even being created, so I get nothing... – JBxOnline Oct 17 '17 at 10:31
  • Have you tried to put COPY . . above composer install command? – Iurii Drozdov Oct 17 '17 at 10:56
  • Yeah - tried that... – JBxOnline Oct 17 '17 at 11:06
  • what do you have at your docker-compose.yml? – Iurii Drozdov Oct 17 '17 at 11:08

6 Answers6

59

This took a lot of digging for someone new to Docker :) Thanks to @iurii-drozdov for pointing me in the right direction with the comment about the docker-compose.yml.

In my docker-compose.yml, I was mounting my host working dir into /var/www/html. This happened after the build. So composer ran the install, installed all the dependencies correctly on build, and then, when running docker-compose up, I was mounting my host dir into the container and wiping all those changes out.

The solution was to run composer install after mounting the volume. It's straight forward enough to do this by simply exec'ing into the container after bringing it up - running composer and any other package managers - then finally running the web server.

However, I found a neater solution. I changed my final CMD in the Dockerfile to:

CMD bash -c "composer install && php artisan serve --host 0.0.0.0 --port 5001"

This will run composer install and bring up the web server as a final part of the docker-compose up.

Credit for the solution here: Docker - Execute command after mounting a volume

JBxOnline
  • 1,339
  • 1
  • 9
  • 10
  • 4
    You can also add the command keyword under your container declaration in docker-compose, like this : command: composer install && php artisan serve --host 0.0.0.0 --port 5001 – Olivier Maurel Oct 11 '18 at 09:45
  • this is a good solution as long as you want the vendor folder as a root – Zenit Nov 28 '18 at 13:14
  • 2
    What if it is Lumen, which doesnt have `php artisan serve`, the CMD will trigger container to exit with status 0. Any solution to this? – Alvin Theodora May 20 '19 at 07:09
  • @Zenit What would you do to prevent that? – Anfelipe Jul 26 '20 at 03:47
  • @Anfelipe try this # Add user for laravel application RUN groupadd -g 1000 www RUN useradd -u 1000 -ms /bin/bash -g www www # Change current user to www USER www – Zenit Jul 27 '20 at 17:30
  • 1
    An ENTRYPOINT script is a good place to add all these extras. – chakatz Sep 14 '20 at 07:44
  • I assume this works for specific images as You wrote in "Your" docker-compose.yml. I found Kien's approach was better and less dependent on base image.. and was reusable as stand-alone. – Rolands.EU Sep 28 '20 at 00:06
  • 7
    Yes but by keep in mind, that your version that install dependencies inside CMD is different solution, where dependencies are NOT PART OF YOUR DOCKER IMAGE. They will now be pulled on every docker run during deployment, and that may lead to errors on deployments. – smentek Oct 29 '20 at 15:53
  • The problem of this solution it is the context. For example if the context it is app/webroot but composer.json it is in app/, this aproach will not work, or maybe is another way to solve this? – R0bertinski Jun 26 '22 at 03:26
  • Thanks for that last part. Since im using `php:8.1-apache`, I finish my Dockerfile with `CMD bash -c "composer install && apache2-foreground"` and it works great – Syclone May 26 '23 at 22:21
29

You can also use the official dockerhub composer image.

This is an example of a multi-stage build with composer running first in a separate container. The resulting /app/vendor is copied to wherever you want in your final image.

FROM composer as builder
WORKDIR /app/
COPY composer.* ./
RUN composer install
...
FROM php:7.1-fpm-alpine
...
COPY --from=builder /app/vendor /var/www/vendor
Phil
  • 1,996
  • 1
  • 19
  • 26
  • i think this is a better solution as it makes use of composition of image, not forcing us to install composer separately – Vincent Nov 10 '21 at 10:10
  • ah but it doesn't work with a mounted directory as OP describes, too bad – Vincent Nov 10 '21 at 10:31
  • Except nothing is copied... `vendor` is not present in the final image – Peter Kionga-Kamau Apr 05 '22 at 19:54
  • Just a note: the linked page says, "we do not want to encourage using Composer as a base image or a production image." The composer image may not have any extensions you need, and the php version may vary. – Charles Wood May 26 '23 at 18:30
22

If you don't want to have the command in the Dockerfile, we found that the simplest way was to add this to our docker-compose file:

composer_installation:
  container_name: composer_installation
  image: composer
  volumes:
    - ./:/app
  command: composer install --ignore-platform-reqs

The update is a bit slow, probably because it is syncing with the PHP container.

Kien Tran
  • 229
  • 2
  • 4
  • 1
    Much more useful as your base image might not contain curl nor php. Kudos for portability! – Jérôme Gillard May 06 '19 at 13:32
  • 1
    @Jérôme Gillard can i use this configuration for production as well? – Prasad Shinde Aug 14 '19 at 13:18
  • The only issue when i did this is after it finished installing it went to delete something and couldn't......can you run this as sudo to avoid the permissions issue? `Could not delete /app/vendor/composer///Examples/templates` – Bill Garrison Dec 03 '20 at 16:59
  • 1
    Container exits immediately after this, so not much use if trying to run a server. – Peter Kionga-Kamau Aug 04 '21 at 20:33
  • Yes sir! After pulling my hair out for a day (:/) I found I need `composer install --ignore-platform-reqs` as a command in docker-compose.yml - it did not run in my Dockerfile – Jono Jul 31 '22 at 19:26
  • 1
    @PrasadShinde This should _not_ be used for production as shown. `--ignore-platform-reqs` means that composer will not halt if you are missing extensions or have the wrong version of PHP. It is intended for e.g. building a container for a testing environment where your dependencies will not actually be used. – Charles Wood May 26 '23 at 18:45
19

As per the recommendations from https://hub.docker.com/_/composer

Run

WORKDIR /your/base/path
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install
jontro
  • 10,241
  • 6
  • 46
  • 71
  • 1
    This is the way and your comment helped me, but there are some important things that need to be added: Installing some packages will require git and unzip, so add ````RUN apt-get install git --yes && \ apt-get install zip unzip --yes````. On the other hand, if you are using a bind and not a volume it is possible that the ````RUN composer install```` command will be executed prematurely and not find your ````composer.json````, to avoid that, run ````composer install```` with ````CMD```` or at your ````entrypoint```` which are called in your container after finishing the image. – Soy César Mora Sep 22 '22 at 14:46
  • If your container is running another command to cmd and you don't want to override it (such as keeping your web server service alive), you can use ````docker inspect```` to check what the command is that is issued by ````CMD```` and concatenate it with a ````&&```` then from your ````composer install```` – Soy César Mora Sep 22 '22 at 16:00
7

I use this command and it generates the vendor :)

docker run --rm -it --volume $(pwd):/app prooph/composer:7.2 install --ignore-platform-reqs
jaumebalust
  • 300
  • 3
  • 9
0

The answer is in this video https://youtu.be/BSvzZvw_T64 min 9:30. In the docker-compose.yml you just have to add the path to the vendor directory in the volume's configuration to avoid overwrite it:

volumes:
  - /var/www/html/vendor