From a33b2bd567b128b9177df985e72831e9bd9c9e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 29 Jun 2023 15:53:34 +0200 Subject: [PATCH] Use a registry for storing docker frontend image --- .castor/infra.php | 30 +++++++++++++------ .castor/utils.php | 17 ++++++----- .github/workflows/ci.yml | 10 +++++++ .gitignore | 2 +- castor.php | 20 +++++++++++-- .../docker/docker-compose.builder.yml | 11 +++---- infrastructure/docker/docker-compose.yml | 16 ++++++++-- infrastructure/docker/services/php/Dockerfile | 17 +++++++---- infrastructure/docker/services/php/entrypoint | 26 ++++++++++++++++ .../docker/services/postgres/Dockerfile | 3 -- .../docker/services/router/Dockerfile | 6 +++- .../docker/services/router/certs/.gitignore | 0 .../docker/services/router/generate-ssl.sh | 5 ++-- .../{etc => }/traefik/dynamic_conf.yaml | 0 .../router/{etc => }/traefik/traefik.yaml | 0 15 files changed, 126 insertions(+), 37 deletions(-) create mode 100755 infrastructure/docker/services/php/entrypoint delete mode 100644 infrastructure/docker/services/postgres/Dockerfile create mode 100644 infrastructure/docker/services/router/certs/.gitignore rename infrastructure/docker/services/router/{etc => }/traefik/dynamic_conf.yaml (100%) rename infrastructure/docker/services/router/{etc => }/traefik/traefik.yaml (100%) diff --git a/.castor/infra.php b/.castor/infra.php index b374f07..9cc22e7 100644 --- a/.castor/infra.php +++ b/.castor/infra.php @@ -79,19 +79,31 @@ function destroy( docker_compose(['down', '--remove-orphans', '--volumes', '--rmi=local'], withBuilder: true); $files = finder() - ->in(variable('root_dir') . '/infrastructure/docker/services/router/etc/ssl/certs/') + ->in(variable('root_dir') . '/infrastructure/docker/services/router/certs/') ->name('*.pem') ->files() ; fs()->remove($files); } +#[AsTask(description: 'Push images to the registry')] +function push(): void +{ + docker_compose(['push'], withBuilder: true); +} + +#[AsTask(description: 'Pull images from the registry')] +function pull(): void +{ + docker_compose(['pull', '-q'], withBuilder: true); +} + #[AsTask(description: 'Generates SSL certificates (with mkcert if available or self-signed if not)')] function generate_certificates( #[AsOption(description: 'Force the certificates re-generation without confirmation', shortcut: 'f')] bool $force = false, ): void { - if (file_exists(variable('root_dir') . '/infrastructure/docker/services/router/etc/ssl/certs/cert.pem') && !$force) { + if (file_exists(variable('root_dir') . '/infrastructure/docker/services/router/certs/cert.pem') && !$force) { io()->comment('SSL certificates already exists.'); io()->note('Run "castor infra:generate-certificates --force" to generate new certificates.'); @@ -99,12 +111,12 @@ function generate_certificates( } if ($force) { - if (file_exists($f = variable('root_dir') . '/infrastructure/docker/services/router/etc/ssl/certs/cert.pem')) { - io()->comment('Removing existing certificates in infrastructure/docker/services/router/etc/ssl/certs/*.pem.'); + if (file_exists($f = variable('root_dir') . '/infrastructure/docker/services/router/certs/cert.pem')) { + io()->comment('Removing existing certificates in infrastructure/docker/services/router/certs/*.pem.'); unlink($f); } - if (file_exists($f = variable('root_dir') . '/infrastructure/docker/services/router/etc/ssl/certs/key.pem')) { + if (file_exists($f = variable('root_dir') . '/infrastructure/docker/services/router/certs/key.pem')) { unlink($f); } } @@ -125,8 +137,8 @@ function generate_certificates( run([ 'mkcert', - '-cert-file', 'infrastructure/docker/services/router/etc/ssl/certs/cert.pem', - '-key-file', 'infrastructure/docker/services/router/etc/ssl/certs/key.pem', + '-cert-file', 'infrastructure/docker/services/router/certs/cert.pem', + '-key-file', 'infrastructure/docker/services/router/certs/key.pem', $rootDomain, "*.{$rootDomain}", ...variable('extra_domains'), @@ -141,9 +153,9 @@ function generate_certificates( return; } - run(['infrastructure/docker/services/router/generate-ssl.sh']); + run(['infrastructure/docker/services/router/generate-ssl.sh'], quiet: true); - io()->success('Successfully generated self-signed SSL certificates in infrastructure/docker/services/router/etc/ssl/certs/*.pem.'); + io()->success('Successfully generated self-signed SSL certificates in infrastructure/docker/services/router/certs/*.pem.'); io()->comment('Consider installing mkcert to generate locally trusted SSL certificates and run "castor infra:generate-certificates --force".'); if ($force) { diff --git a/.castor/utils.php b/.castor/utils.php index 69147c9..5292217 100644 --- a/.castor/utils.php +++ b/.castor/utils.php @@ -47,7 +47,7 @@ function about(): void } #[AsTask(description: 'Opens a shell (bash) into a builder container')] -function builder(string $user = 'app'): void +function builder(): void { $c = get_context() ->withTimeout(null) @@ -56,7 +56,7 @@ function builder(string $user = 'app'): void ->withQuiet() ->withAllowFailure() ; - docker_compose_run('bash', c: $c, user: $user); + docker_compose_run('bash', c: $c); } #[AsContext(default: true)] @@ -98,15 +98,18 @@ function create_default_context(): Context $data['user_id'] = 1000; } - // @phpstan-ignore-next-line - return new Context($data, pty: 'dev' === $data['env']); + return new Context( + // @phpstan-ignore-next-line + $data, + pty: 'dev' === $data['env'], + environment: create_default_environment(), + ); } function docker_compose_run( string $runCommand, Context $c = null, string $service = 'builder', - string $user = 'app', bool $noDeps = true, string $workDir = null, bool $portMapping = false, @@ -115,7 +118,6 @@ function docker_compose_run( $command = [ 'run', '--rm', - '-u', $user, ]; if ($noDeps) { @@ -156,9 +158,10 @@ function docker_compose(array $subCommand, Context $c = null, bool $withBuilder 'PROJECT_DIRECTORY' => variable('project_directory'), 'PROJECT_ROOT_DOMAIN' => variable('root_domain'), 'PROJECT_DOMAINS' => $domains, + 'USER_ID' => variable('user_id'), 'COMPOSER_CACHE_DIR' => variable('composer_cache_dir'), 'PHP_VERSION' => variable('php_version'), - ], false) + ], true) ; $command = [ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 046edfe..03ba0c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,7 @@ name: Continuous Integration permissions: contents: read + packages: read jobs: ci: @@ -17,8 +18,17 @@ jobs: runs-on: ubuntu-latest env: BUILDKIT_PROGRESS: plain + DOCKER_BUILDKIT: 1 CI: 1 steps: + - + name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: 'ghcr.io' + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index c3d26ab..0b85313 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ /.castor.stub.php /infrastructure/docker/.env /infrastructure/docker/docker-compose.override.yml -/infrastructure/docker/services/router/etc/ssl/certs/* +/infrastructure/docker/services/router/certs/* diff --git a/castor.php b/castor.php index 181cb73..07a4c65 100644 --- a/castor.php +++ b/castor.php @@ -24,11 +24,27 @@ function create_default_variables(): array ]; } +/** + * @return array + */ +function create_default_environment(): array +{ + return [ + 'BUILDER_VERSION' => 'latest', + 'FRONTEND_VERSION' => 'latest', + 'ROUTER_VERSION' => 'latest', + ]; +} + #[AsTask(description: 'Builds and starts the infrastructure, then install the application (composer, yarn, ...)')] -function start(): void +function start(bool $build = false): void { infra\generate_certificates(false); - infra\build(); + if ($build) { + infra\build(); + } else { + infra\pull(); + } infra\up(); cache_clear(); install(); diff --git a/infrastructure/docker/docker-compose.builder.yml b/infrastructure/docker/docker-compose.builder.yml index 14bdd4f..b72f25a 100644 --- a/infrastructure/docker/docker-compose.builder.yml +++ b/infrastructure/docker/docker-compose.builder.yml @@ -5,19 +5,20 @@ volumes: services: builder: + image: "ghcr.io/jolicode/monologue/builder:${BUILDER_VERSION:-latest}" build: context: services/php target: builder + # cache_to: + # - "ghcr.io/jolicode/monologue/builder:${BUILDER_VERSION:-latest}" + # cache_from: + # - "ghcr.io/jolicode/monologue/builder:${BUILDER_VERSION:-latest}" depends_on: - postgres environment: - COMPOSER_MEMORY_LIMIT=-1 - # The following list contains the common environment variables exposed by CI platforms + - UID=${USER_ID} - GITHUB_ACTIONS - - CI # Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari - - CONTINUOUS_INTEGRATION # Travis CI, Cirrus CI - - BUILD_NUMBER # Jenkins, TeamCity - - RUN_ID # TaskCluster, dsari volumes: - "../../${PROJECT_DIRECTORY}:/home/app/application:cached" - "${COMPOSER_CACHE_DIR}:/home/app/.composer/cache" diff --git a/infrastructure/docker/docker-compose.yml b/infrastructure/docker/docker-compose.yml index ad15301..04ae5cb 100644 --- a/infrastructure/docker/docker-compose.yml +++ b/infrastructure/docker/docker-compose.yml @@ -5,15 +5,27 @@ volumes: services: router: - build: services/router + image: "ghcr.io/jolicode/monologue/router:${ROUTER_VERSION:-latest}" + build: + context: services/router + # cache_to: + # - "ghcr.io/jolicode/monologue/router:${ROUTER__VERSION:-latest}" + # cache_from: + # - "ghcr.io/jolicode/monologue/router:${ROUTER__VERSION:-latest}" volumes: - "/var/run/docker.sock:/var/run/docker.sock" + - "./services/router/certs:/etc/ssl/certs" network_mode: host frontend: + image: "ghcr.io/jolicode/monologue/frontend:${FRONTEND_VERSION:-latest}" build: context: services/php target: frontend + # cache_to: + # - "ghcr.io/jolicode/monologue/frontend:${FRONTEND_VERSION:-latest}" + # cache_from: + # - "ghcr.io/jolicode/monologue/frontend:${FRONTEND_VERSION:-latest}" depends_on: - postgres volumes: @@ -29,7 +41,7 @@ services: - "traefik.http.routers.${PROJECT_NAME}-frontend-unsecure.middlewares=redirect-to-https@file" postgres: - build: services/postgres + image: postgres:15.2 environment: POSTGRES_PASSWORD: monologue POSTGRES_USER: monologue diff --git a/infrastructure/docker/services/php/Dockerfile b/infrastructure/docker/services/php/Dockerfile index 613cd8c..6073619 100644 --- a/infrastructure/docker/services/php/Dockerfile +++ b/infrastructure/docker/services/php/Dockerfile @@ -1,12 +1,16 @@ FROM debian:11.7-slim as php-base +LABEL org.opencontainers.image.source https://github.com/jolicode/monologue + RUN apt-get update \ && apt install -y --no-install-recommends \ curl \ ca-certificates \ gnupg \ && curl -s https://packages.sury.org/php/apt.gpg | gpg --dearmor > /usr/share/keyrings/deb.sury.org-php.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php bullseye main" > /etc/apt/sources.list.d/sury.list + && echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php bullseye main" > /etc/apt/sources.list.d/sury.list \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -33,14 +37,19 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* # Fake user to maps with the one on the host +COPY entrypoint / ARG USER_ID -RUN addgroup --gid 1000 app && \ - adduser --system --uid $USER_ID --home /home/app --shell /bin/bash app +RUN addgroup --gid $USER_ID app && \ + adduser --system --uid $USER_ID --home /home/app --shell /bin/bash app && \ + curl -Ls https://github.com/tianon/gosu/releases/download/1.16/gosu-amd64 | \ + install /dev/stdin /usr/local/bin/gosu && \ + sed "s/{{ application_user }}/app/g" -i /entrypoint # Configuration COPY base/php-configuration /etc/php/${PHP_VERSION} WORKDIR /home/app/application +ENTRYPOINT [ "/entrypoint" ] FROM php-base as frontend @@ -94,5 +103,3 @@ RUN mkdir -p "/home/app/.composer/cache" \ ENV PATH="$PATH:/home/app/application/tools/php-cs-fixer/vendor/bin" ENV PATH="$PATH:/home/app/application/tools/phpstan/vendor/bin" - -USER app diff --git a/infrastructure/docker/services/php/entrypoint b/infrastructure/docker/services/php/entrypoint new file mode 100755 index 0000000..1f15e84 --- /dev/null +++ b/infrastructure/docker/services/php/entrypoint @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e +set -u + +if [ $(id -u) != 0 ]; then + echo "Running this image as non root is not allowed" + exit 1 +fi + +: "${UID:=0}" +: "${GID:=${UID}}" + +if [ "$#" = 0 ]; then + set -- "$(command -v bash 2>/dev/null || command -v sh)" -l +fi + +if [ "$UID" != 0 ]; then + usermod -u "$UID" "{{ application_user }}" >/dev/null 2>/dev/null && { + groupmod -g "$GID" "{{ application_user }}" >/dev/null 2>/dev/null || + usermod -a -G "$GID" "{{ application_user }}" >/dev/null 2>/dev/null + } + set -- gosu "${UID}:${GID}" "${@}" +fi + +exec "$@" diff --git a/infrastructure/docker/services/postgres/Dockerfile b/infrastructure/docker/services/postgres/Dockerfile deleted file mode 100644 index 49aa9ee..0000000 --- a/infrastructure/docker/services/postgres/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM postgres:15.2 - -EXPOSE 5432 diff --git a/infrastructure/docker/services/router/Dockerfile b/infrastructure/docker/services/router/Dockerfile index 5eb3aa5..b9d7fef 100644 --- a/infrastructure/docker/services/router/Dockerfile +++ b/infrastructure/docker/services/router/Dockerfile @@ -1,3 +1,7 @@ FROM traefik:v2.7 -COPY etc/. /etc/ +LABEL org.opencontainers.image.source https://github.com/jolicode/monologue + +COPY traefik /etc/traefik + +VOLUME [ "/etc/ssl/certs" ] diff --git a/infrastructure/docker/services/router/certs/.gitignore b/infrastructure/docker/services/router/certs/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/docker/services/router/generate-ssl.sh b/infrastructure/docker/services/router/generate-ssl.sh index ffd3bf9..3ff4fb0 100755 --- a/infrastructure/docker/services/router/generate-ssl.sh +++ b/infrastructure/docker/services/router/generate-ssl.sh @@ -4,9 +4,10 @@ BASE=$(dirname $0) -rm -rf mkdir $BASE/certs/ -CERTS_DIR=$BASE/etc/ssl/certs +CERTS_DIR=$BASE/certs + +rm -rf mkdir $CERTS_DIR mkdir -p $CERTS_DIR diff --git a/infrastructure/docker/services/router/etc/traefik/dynamic_conf.yaml b/infrastructure/docker/services/router/traefik/dynamic_conf.yaml similarity index 100% rename from infrastructure/docker/services/router/etc/traefik/dynamic_conf.yaml rename to infrastructure/docker/services/router/traefik/dynamic_conf.yaml diff --git a/infrastructure/docker/services/router/etc/traefik/traefik.yaml b/infrastructure/docker/services/router/traefik/traefik.yaml similarity index 100% rename from infrastructure/docker/services/router/etc/traefik/traefik.yaml rename to infrastructure/docker/services/router/traefik/traefik.yaml