Sisir
Sisir

Reputation: 2816

Docker project with php composer, installs composer packages in root directory instead of vendor

I have a php/wordpress project which requires composer. The project setup is simple and minimal.

docker-compose.yaml

version: "3.9"

services:
  # Database
  clearlaw-mysql1:
    image: mysql:8
    volumes:
      - database:/var/lib/mysql
    restart: on-failure
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    networks:
      - clearlaw
  # Wordpress
  clearlaw-wp1:
    container_name: clearlaw-wp1
    build:
      context: .
    depends_on:
      - clearlaw-mysql
    image: wordpress:latest
    ports:
      - 10002:80
    restart: unless-stopped
      CLI_MULTISITE_DEBUG: 1
      CLI_MULTISITE_DEBUG_DISPLAY: 1
      CLI_MULTISITE_DB_HOST: clarlaw-mysql:3306
      CLI_MULTISITE_DB_NAME: wordpress
      CLI_MULTISITE_DB_USER: wordpress
      CLI_MULTISITE_DB_PASSWORD: wordpress
    networks:
      - clearlaw
  clearlaw-adminer1:
    image: adminer
    ports:
      - 10003:8080
    restart: unless-stopped
    networks:
      - clearlaw
networks:
  clearlaw:
volumes:
  database:

Dockerfile

FROM wordpress:latest

# INSTALL AND UPDATE COMPOSER
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer self-update


COPY composer.json .
RUN composer install --prefer-dist
RUN composer dump-autoload

COPY . .
EXPOSE 80

composer.json

{
  "require": {
    "vlucas/phpdotenv": "^v2.6.7",
    "dompdf/dompdf": "^1.0"
  }
}

When I run this setup I get fatal error autoload.php file is not where it should be (/vendor/autoload). instead it is in the root directory along with all the installed pacakges. The vendor directory exists however it is empty.

Example Directory structure:

-- autoload.php
   vendor # empty
   composer
   wp-content
   wp-admin
   wp-includes
   # all other files

What I've Tried?

I have tried adding vendor directory explicitly in composer.json but it didn't help

{
  "config": {
    "vendor-dir": "vendor"
  },
  "require": {
    "vlucas/phpdotenv": "^v2.6.7",
    "dompdf/dompdf": "^1.0"
  }
}

Update

I have created this repository for you to quick test https://github.com/prionkor/wp-composer-test

Upvotes: 1

Views: 2622

Answers (1)

hakre
hakre

Reputation: 198101

The wordpress:latest container has a volume specified at /var/www/html which is also the containers working directory.

When you bring your docker-compose up, the (anonymous) volume is created and the containers entry-point script (/usr/local/bin/docker-entrypoint.sh) copies the WordPress sources into the new volume.

In general only after that a composer install with the vendor dir at /var/www/html/vendor would not be discarded.

As you have the composer install within the Dockerfile building FROM wordpress:latest the vendor folder is created but then discarded. It is either too early or too late depending on from where you look.

Therefore, you can drop the RUN composer ... instructions from your Dockerfile, these are effectively only lengthen the build time.

FROM wordpress:latest

# INSTALL AND UPDATE COMPOSER
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer self-update

COPY . .
EXPOSE 80

Then do the composer install (dump-autoload is not necessary after it) and exec the base containers entrypoint when starting the container:

  # WordPress
    build:
      context: .
    command: /bin/bash -c "set -ex; composer update --prefer-dist; exec docker-entrypoint.sh apache2-foreground"

Then Composer installs into the volume, effectively at /var/www/html/vendor.

The original WordPress container setup will give a warning that the folder is not empty any longer, but it does not prevent the initialization of WordPress, so you can ignore it:

WARNING: /var/www/html is not empty! (copying anyhow)

Composer then installs the build contexts composer.json/composer.lock when you bring the project up initially (if the composer.lock file is leading in the project outside the container use composer install instead of composer update).

You then can run

docker-compose up -d --build

to recreate the WordPress service container from scratch and Composer populates the vendor folder again.

Commonly this is the time when you can greatly benefit from a command runner, for example a Makefile. You then have the standard operations in your project at your fingertips.

Here a composer-update goal:

wordpress := clearlaw-wp1

cu: composer-update;
composer-update:
    tar -c -f- composer.json \
      | docker cp - $(wordpress):/var/www/html
    docker-compose exec $(wordpress) composer update

Then alias the make command for that file in your shell (e.g. alias dc="make -C $(pwd) -f Makefile") and then you only need to type dc cu to perform the update.

You can extend it further for other operations, and you can also experiment with the volumes as you have commented them out in your example. E.g. you can mount things in read-only inside the existing volume /var/www/html, just not directly at that place.

You can also mount single files. E.g. to extend on the composer example, you could as well mount the composer.json (and composer.lock) file into the container, then you can spare the tar-pipe to docker cp. Which also serves as an example how you can get files out of the container as it works in both directions.

    command: /bin/bash -c "set -x; composer update --prefer-dist; exec docker-entrypoint.sh apache2-foreground"
    volumes: ["./composer.json:/var/www/html/composer.json:ro"]

You then can run

docker-compose up -d --build

again to recreate the container and trigger the Composer update.


And a side-note: Take a bit of care with the vlucas/phpdotenv package, it can become easily incompatible with docker / docker-compose if you don't follow the standard syntax rules for dot-env files (shell, docker etc.). Normally you also don't need it for a container setup.

Upvotes: 1

Related Questions