ARG COMPOSER_VERSION ARG TARGET_PHP_VERSION FROM composer:${COMPOSER_VERSION} as composer FROM php:${TARGET_PHP_VERSION}-fpm as base # make build args available as ENV variables to downstream images # so that we don't have to pass the same build args again ARG APP_USER_ID ARG APP_GROUP_ID ARG APP_USER_NAME ARG APP_GROUP_NAME ARG APP_CODE_PATH ARG ENV ENV APP_USER_ID=${APP_USER_ID} ENV APP_GROUP_ID=${APP_GROUP_ID} ENV APP_USER_NAME=${APP_USER_NAME} ENV APP_GROUP_NAME=${APP_GROUP_NAME} ENV APP_CODE_PATH=${APP_CODE_PATH} ENV TARGET_PHP_VERSION=${TARGET_PHP_VERSION} ENV ENV=${ENV} RUN addgroup -gid $APP_GROUP_ID $APP_GROUP_NAME && \ adduser --disabled-password --uid $APP_USER_ID --shell /bin/bash --ingroup $APP_GROUP_NAME $APP_USER_NAME && \ mkdir -p $APP_CODE_PATH && \ chown $APP_USER_NAME: $APP_CODE_PATH # install git-secret # @see https://git-secret.io/installation#alpine ADD https://gitsecret.jfrog.io/artifactory/api/security/keypair/public/repositories/git-secret-apk /etc/apk/keys/git-secret-apk.rsa.pub # FYI, we are NOT using a cache mount to store the apk cache via # RUN --mount=type=cache,target=/var/cache/apk ln -vs /var/cache/apk /etc/apk/cache && \ # @see https://github.com/FernandoMiguel/BuildKit#new-dockerfile # @see https://wiki.alpinelinux.org/wiki/Local_APK_cache#Enabling_Local_Cache_on_HDD_installs # because we run --update anyways to get the latest files RUN apt update && \ apt install -y \ bash \ git \ git-secret \ # required for git-secret gawk \ gnupg \ make \ strace \ sudo \ vim # Install intl RUN apt-get update && apt-get install -y \ libicu-dev \ libssl-dev \ libcurl4-openssl-dev \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libxml2-dev \ libmagickwand-dev \ git \ zlib1g-dev \ unzip \ libzip-dev \ mupdf-tools \ imagemagick \ libmcrypt-dev # Install fileinfo RUN docker-php-ext-install -j$(nproc) fileinfo # Install intl RUN docker-php-ext-install -j$(nproc) intl # Install mongodb RUN pecl install mongodb \ && docker-php-ext-enable mongodb # Install mcrypt RUN pecl install mcrypt \ && docker-php-ext-enable mcrypt # Install curl RUN docker-php-ext-install -j$(nproc) curl # Install Zip RUN docker-php-ext-install zip # Install gd RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) gd # Install soap RUN docker-php-ext-install -j$(nproc) soap # Install imagick RUN pecl install imagick \ && docker-php-ext-enable imagick # Install mysql RUN docker-php-ext-install -j$(nproc) pdo_mysql # Install opcache RUN docker-php-ext-install -j$(nproc) opcache RUN apt-get update && apt-get install -y \ libc-client-dev libkrb5-dev libldap2-dev && \ rm -r /var/lib/apt/lists/* # Install ldap RUN docker-php-ext-install -j$(nproc) ldap RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \ docker-php-ext-install -j$(nproc) imap # make bash default shell RUN sed -e 's;/bin/ash$;/bin/bash;g' -i /etc/passwd COPY ./.docker/images/php/base/conf.d/zz-app.ini $PHP_INI_DIR/conf.d/zz-app.ini COPY ./.docker/images/php/base/conf.d/zz-app-${ENV}.ini $PHP_INI_DIR/conf.d/zz-ppp-${ENV}.ini COPY ./.docker/images/php/base/.bashrc /home/${APP_USER_NAME}/.bashrc COPY ./.docker/images/php/base/.bashrc /root/.bashrc COPY --from=composer /usr/bin/composer /usr/local/bin/composer # Fix git permission issue: # `git` introduced a security feature to throw an error if the parent directory # of the `.git` directory is owned by another user. # @see https://github.blog/2022-04-12-git-security-vulnerability-announced/ # @see https://github.com/actions/checkout/issues/760 # # Since we might not have full control over the owner # ( see e.g. https://github.com/docker/for-win/issues/12742 ) # we will add the $APP_CODE_PATH as a "safe" directory to the global git config via # git config --system --add safe.directory "/path/to/git/parent/folder" # @see https://git-scm.com/docs/git-config/2.36.0#Documentation/git-config.txt-safedirectory # # Without this fix, git-secret will emit the error # git-secret: abort: not in dir with git repo. Use 'git init' or 'git clone', then in repo use 'git secret init' RUN git config --system --add safe.directory "$APP_CODE_PATH" WORKDIR $APP_CODE_PATH FROM base as codebase # By only copying the composer files required to run composer install # the layer will be cached and only invalidated when the composer dependencies are changed COPY ./new/composer.json /dependencies/new/ COPY ./new/composer.lock /dependencies/new/ # use a cache mount to cache the composer dependencies # this is essentially a cache that lives in Docker BuildKit (i.e. has nothing to do with the host system) RUN --mount=type=cache,target=/tmp/.composer \ cd /dependencies/new && \ # COMPOSER_HOME=/tmp/.composer sets the home directory of composer that # also controls where composer looks for the cache # so we don't have to download dependencies again (if they are cached) # @see https://stackoverflow.com/a/60518444 for the correct if-then-else syntax: # - end all commands with ; \ # - except THEN and ELSE if [ "$ENV" == "prod" ] ; \ then \ # on production, we don't want test dependencies COMPOSER_HOME=/tmp/.composer composer install --no-scripts --no-plugins --no-progress -o --no-dev; \ else \ COMPOSER_HOME=/tmp/.composer composer install --no-scripts --no-plugins --no-progress -o; \ fi # copy the full codebase COPY . /codebase # move the dependencies RUN mv /dependencies/vendor /codebase/new/vendor # remove files we don't require in the image to keep the image size small RUN cd /codebase && \ rm -rf .docker/ .build/ .infrastructure/ && \ if [ "$ENV" == "prod" ] ; \ then \ # on production, we don't want tests rm -rf tests/; \ fi # Remove all secrets that are NOT required for the given ENV: # `find /codebase/.secrets -type f -print` lists all files in the .secrets directory # `grep -v "/\(shared\|$ENV\)/"` matches only the files that are NOT in the shared/ or $ENV/ (e.g. prod/) directories # `grep -v ".secret\$"` ensures that we remove all files that are NOT ending in .secret # FYI: # the "$" has to be escaped with a "\" # "Escaping is possible by adding a \ before the variable" # @see https://docs.docker.com/engine/reference/builder/#environment-replacement # `xargs rm -f` retrieves the remaining file and deletes them # FYI: # `xargs` is necessary to convert the stdin to args for `rm` # @see https://stackoverflow.com/a/20307392/413531 # the `-f` flag is required so that `rm` doesn't fail if no files are matched RUN find /codebase/.secrets -type f -print | grep -v "/\(shared\|$ENV\)/" | xargs rm -f && \ find /codebase/.secrets -type f -print | grep -v ".secret\$" | xargs rm -f && \ # list the remaining files for debugging purposes find /codebase/.secrets -type f -print # We need a git repository for git-secret to work (can be an empty one) RUN cd /codebase && \ git init FROM base as prod # We will use a custom ENTRYPOINT to decrypt the secrets when the container starts. # This way, we can store the secrets in their encrypted form directly in the image. # Note: Because we defined a custom ENTRYPOINT, the default CMD of the base image # will be overriden. Thus, we must explicitly re-define it here via `CMD ["/bin/sh"]`. # This behavior is described in the docs as: # "If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value." # @see https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact COPY ./.docker/images/php/base/decrypt-secrets.sh /decrypt-secrets.sh RUN chmod +x /decrypt-secrets.sh CMD ["/bin/sh"] ENTRYPOINT ["/decrypt-secrets.sh"] COPY --from=codebase --chown=$APP_USER_NAME:$APP_GROUP_NAME /codebase $APP_CODE_PATH COPY --chown=$APP_USER_NAME:$APP_GROUP_NAME ./.build/build-info $APP_CODE_PATH/build-info FROM base as ci COPY --from=codebase --chown=$APP_USER_NAME:$APP_GROUP_NAME /codebase $APP_CODE_PATH FROM base as local # add app user to sudoers # see https://ostechnix.com/add-delete-and-grant-sudo-privileges-to-users-in-alpine-linux/ for adding sudo # see https://askubuntu.com/a/340669 for not requiring a sudo pw RUN echo "root ALL=(ALL) NOPASSWD: ALL " | tee -a "/etc/sudoers.d/users" && \ echo "${APP_USER_NAME} ALL=(ALL) NOPASSWD: ALL " | tee -a "/etc/sudoers.d/users" RUN pecl install xdebug