-1

My application is successfully deployed to Google Cloud Run and is a PHP-based website. The site makes use of Google's sign-in API which requires composer google/apiclient.

I can't figure out how to build the required /vendor folder structure (i.e. installing composer) within the Dockerfile. My workaround currently is to include the /vendor folder structure in my code and build that as part of the cloud run deploy ....

My Dockerfile (which contains no reference to the requirement for composer and the google/apiclient) looks like this:

# Use the official PHP image.
# https://hub.docker.com/_/php
FROM php:8.0-apache

# Configure PHP for Cloud Run.
# Precompile PHP code with opcache.
RUN docker-php-ext-install -j "$(nproc)" opcache
RUN docker-php-ext-install -j "$(nproc)" mysqli
RUN set -ex; \
  { \
    echo "; Cloud Run enforces memory & timeouts"; \
    echo "memory_limit = -1"; \
    echo "max_execution_time = 0"; \
    echo "; File upload at Cloud Run network limit"; \
    echo "upload_max_filesize = 32M"; \
    echo "post_max_size = 32M"; \
    echo "; Configure Opcache for Containers"; \
    echo "opcache.enable = On"; \
    echo "opcache.validate_timestamps = Off"; \
    echo "; Configure Opcache Memory (Application-specific)"; \
    echo "opcache.memory_consumption = 32"; \
  } > "$PHP_INI_DIR/conf.d/cloud-run.ini"

# Copy in custom code from the host machine.
WORKDIR /var/www/html
COPY . ./

# Use the PORT environment variable in Apache configuration files.
# https://cloud.google.com/run/docs/reference/container-contract#port
RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf

# Configure PHP for development.
# Switch to the production php.ini for production operations.
# RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# https://github.com/docker-library/docs/blob/master/php/README.md#configuration
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

My composer.json file:

{
    "require": {
        "google/apiclient": "^2.11"
    }
}

I found this post, and attempted the multi-stage build approach as mentioned here by including the following at the end of my Dockerfile, but this throws an error on build:

FROM composer as builder
WORKDIR /app/
COPY composer.* ./
RUN composer install
COPY --from=builder /app/vendor /var/www/html/vendor

When I attempted to view the logs, the console informed me that I didn't have the required permissions. I added Cloud Build Viewer and Cloud Build Editor as per this post in order to view the error.

All preceding steps complete and I can see the The error states:

Step 13/13 : COPY --from=builder /app/vendor /var/www/html/vendor
invalid from flag value builder: pull access denied for builder, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
ERROR
ERROR: build step 0 "gcr.io/cloud-builders/docker" failed: step exited with non-zero status: 1

How do I reference the container with the successful composer build? Do I need another step, or is the COPY statement in the wrong place?

MuppetDance
  • 144
  • 10

2 Answers2

1

You don't need to set up a multi-stage build to run composer, you can simply install and run composer at the php:8.0-apache Dockerfile.

There are some options.

Installing composer with no composer.json file:

RUN apt update && apt install -y zip
RUN curl -sS https://getcomposer.org/installer | php \
    && mv composer.phar /usr/local/bin/composer \
    && chmod +x /usr/local/bin/composer
RUN composer require google/apiclient:^2.11 \
    && composer clear-cache

Installing composer with composer.json file:

RUN apt update && apt install -y zip
RUN curl -sS https://getcomposer.org/installer | php \
    && mv composer.phar /usr/local/bin/composer \
    && chmod +x /usr/local/bin/composer
RUN composer install \
    && composer clear-cache

composer.json:

{
    "require": {
        "google/apiclient": "^2.11"
    }
}

With that, composer will be installed along with the deps in /var/www/html since it is the WORKDIR.

Juan Fontes
  • 738
  • 3
  • 16
  • Brilliant! That considerably simplifies my Dockerfile and build. I'm wondering if I should do something with the composer.phar which is installed in /var/www/html? In a normal install, composer.phar would go into /usr/local/bin/composer? – MuppetDance Jan 30 '22 at 13:33
0

I think the problem was that I was attempting to copy the composer install (builder) from itself. I found this link which suggested that the composer install could be done first and then referenced within the php build (which also copies my source files).

My updated Dockerfile now reads as follows, and it successfully builds!

#Install composer
FROM composer:latest as composer
WORKDIR /var/www/html
#COPY the composer.json and composer.lock files to the working directory
COPY composer.* ./
RUN composer install

# Use the official PHP image.
# https://hub.docker.com/_/php
FROM php:8.0-apache
#Copy the composer install (/vendor folders) into the working directory
COPY --from=composer /var/www/html .

# Use the official PHP image.
# https://hub.docker.com/_/php
FROM php:8.0-apache

# Configure PHP for Cloud Run.
# Precompile PHP code with opcache.
RUN docker-php-ext-install -j "$(nproc)" opcache
RUN docker-php-ext-install -j "$(nproc)" mysqli
RUN set -ex; \
  { \
    echo "; Cloud Run enforces memory & timeouts"; \
    echo "memory_limit = -1"; \
    echo "max_execution_time = 0"; \
    echo "; File upload at Cloud Run network limit"; \
    echo "upload_max_filesize = 32M"; \
    echo "post_max_size = 32M"; \
    echo "; Configure Opcache for Containers"; \
    echo "opcache.enable = On"; \
    echo "opcache.validate_timestamps = Off"; \
    echo "; Configure Opcache Memory (Application-specific)"; \
    echo "opcache.memory_consumption = 32"; \
  } > "$PHP_INI_DIR/conf.d/cloud-run.ini"

# Copy in custom code from the host machine.
WORKDIR /var/www/html
COPY . ./

# Use the PORT environment variable in Apache configuration files.
# https://cloud.google.com/run/docs/reference/container-contract#port
RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf

# Configure PHP for development.
# Switch to the production php.ini for production operations.
# RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# https://github.com/docker-library/docs/blob/master/php/README.md#configuration
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

Presumably I could equally have called the referenced the PHP build step as FROM php:8.0-apache as PHPBuild and then copied the result into the composer build step with COPY --FROM PHPBuild /var/www/html .?

MuppetDance
  • 144
  • 10