diff --git a/.dockerignore b/.dockerignore index fc10ce7dc..7cd4dd2be 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,7 @@ !/.git/config # Unneeded application files -/.docker/data/README.md +/.docker /.github /docs /fixtures @@ -15,6 +15,22 @@ /scripts /tests +# IDE / editor state +/.idea +/.vscode + +# Compose / task runner / dev tooling configs (not consumed at runtime) +/docker-compose*.yml +/Taskfile.yml +/playwright.config.ts + +# CI / quality-tool configs (not consumed at runtime) +/phpstan.dist.neon +/phpunit.xml.dist +/psalm.xml +/psalm-baseline.xml +/rector.php + ###> symfony/framework-bundle ### /.env.local /.env.local.php diff --git a/.github/workflows/apispec.yaml b/.github/workflows/apispec.yaml index cf3c3eac7..152f5b23c 100644 --- a/.github/workflows/apispec.yaml +++ b/.github/workflows/apispec.yaml @@ -11,7 +11,7 @@ jobs: name: API Specification validation steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml new file mode 100644 index 000000000..91bdc9aca --- /dev/null +++ b/.github/workflows/build-images.yml @@ -0,0 +1,113 @@ +--- +name: Build docker images + +on: + push: + branches: + - develop + tags: + - "*" + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v6 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta (API) + id: meta-api + uses: docker/metadata-action@v6 + with: + images: ghcr.io/os2display/display-api-service + tags: | + # develop branch → 'develop' + type=ref,event=branch + # any tag → tag literal (e.g. '3.0.0', '3.0.0-RC1' for release + # candidates cut from release/X.Y.Z) + type=ref,event=tag + # also emit canonical semver tag for valid semver-shaped tags, + # so '3.0.0' and '3.0.0-RC1' both produce a properly-typed entry. + type=semver,pattern={{version}} + # latest=auto adds ':latest' only for non-prerelease semver tags, + # so '3.0.0' updates ':latest' but '3.0.0-RC1' does not. + flavor: | + latest=auto + + - name: Build and push (API) + uses: docker/build-push-action@v7 + with: + context: ./infrastructure/display-api-service/ + file: ./infrastructure/display-api-service/Dockerfile + build-contexts: | + repository-root=. + # Use the version computed by docker/metadata-action so APP_VERSION + # matches the image tag for every trigger ('develop' / '3.0.0' / + # '3.0.0-RC1') without bespoke string handling per trigger. + build-args: | + APP_VERSION=${{ steps.meta-api.outputs.version }} + APP_RELEASE_VERSION=${{ steps.meta-api.outputs.version }} + APP_RELEASE_TIMESTAMP=${{ github.run_number }} + push: true + tags: ${{ steps.meta-api.outputs.tags }} + labels: ${{ steps.meta-api.outputs.labels }} + provenance: mode=max + sbom: true + platforms: linux/amd64,linux/arm64 + # mode=max caches builder stages (client_app_builder, api_app_builder), + # which is what makes multi-arch builds practical to cache at all. + # Scoped per image so API and Nginx caches don't evict each other. + cache-from: type=gha,scope=display-api-service + cache-to: type=gha,mode=max,scope=display-api-service + + - name: Docker meta (Nginx) + id: meta-nginx + uses: docker/metadata-action@v6 + with: + images: ghcr.io/os2display/display-api-service-nginx + tags: | + # develop branch → 'develop' + type=ref,event=branch + # any tag → tag literal (e.g. '3.0.0', '3.0.0-RC1' for release + # candidates cut from release/X.Y.Z) + type=ref,event=tag + # also emit canonical semver tag for valid semver-shaped tags, + # so '3.0.0' and '3.0.0-RC1' both produce a properly-typed entry. + type=semver,pattern={{version}} + flavor: | + latest=auto + + - name: Build and push (Nginx) + uses: docker/build-push-action@v7 + with: + context: ./infrastructure/nginx/ + file: ./infrastructure/nginx/Dockerfile + build-contexts: | + repository-root=. + build-args: | + APP_VERSION=${{ steps.meta-nginx.outputs.version }} + APP_RELEASE_VERSION=${{ steps.meta-nginx.outputs.version }} + APP_RELEASE_TIMESTAMP=${{ github.run_number }} + push: true + tags: ${{ steps.meta-nginx.outputs.tags }} + labels: ${{ steps.meta-nginx.outputs.labels }} + provenance: mode=max + sbom: true + platforms: linux/amd64,linux/arm64 + cache-from: type=gha,scope=display-api-service-nginx + cache-to: type=gha,mode=max,scope=display-api-service-nginx diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 483da6e95..63638c2d1 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -18,7 +18,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index b068c235a..8787034df 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | docker network create frontend docker compose run --rm phpfpm composer validate --strict @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | docker network create frontend docker compose run --rm phpfpm composer install diff --git a/.github/workflows/composer_install.yaml b/.github/workflows/composer_install.yaml index cc69d8b13..bdce83d6f 100644 --- a/.github/workflows/composer_install.yaml +++ b/.github/workflows/composer_install.yaml @@ -11,7 +11,7 @@ jobs: name: Composer install in prod mode steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/docker_build_images_from_develop.yml b/.github/workflows/docker_build_images_from_develop.yml deleted file mode 100644 index 0becba3b0..000000000 --- a/.github/workflows/docker_build_images_from_develop.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -on: - push: - branches: - - "develop" - -# This Action builds to os2display/* using ./infrastructure/* -name: Build docker image (develop) - -jobs: - docker: - runs-on: ubuntu-latest - env: - APP_VERSION: develop - COMPOSER_ALLOW_SUPERUSER: 1 - steps: - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USER }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - # Build api - - name: Docker meta (API) - id: meta-api - uses: docker/metadata-action@v5 - with: - images: os2display/display-api-service - - - name: Build and push (API) - uses: docker/build-push-action@v6 - with: - context: ./infrastructure/display-api-service/ - file: ./infrastructure/display-api-service/Dockerfile - build-args: | - VERSION=${{ env.APP_VERSION }} - push: true - tags: ${{ steps.meta-api.outputs.tags }} - labels: ${{ steps.meta-api.outputs.labels }} - provenance: mode=max - sbom: true - platforms: linux/amd64,linux/arm64 - - # Build nginx (depends on api build) - - name: Docker meta (Nginx) - id: meta-nginx - uses: docker/metadata-action@v5 - with: - images: os2display/display-api-service-nginx - - - name: Build and push (Nginx) - uses: docker/build-push-action@v6 - with: - context: ./infrastructure/nginx/ - file: ./infrastructure/nginx/Dockerfile - build-args: | - APP_VERSION=${{ env.APP_VERSION }} - push: true - tags: ${{ steps.meta-nginx.outputs.tags }} - labels: ${{ steps.meta-nginx.outputs.labels }} - provenance: mode=max - sbom: true - platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/docker_build_images_from_tag.yml b/.github/workflows/docker_build_images_from_tag.yml deleted file mode 100644 index 4b441be89..000000000 --- a/.github/workflows/docker_build_images_from_tag.yml +++ /dev/null @@ -1,68 +0,0 @@ ---- -on: - push: - tags: - - "*" - -# This Action builds to os2display/* using ./infrastructure/* -name: Build docker image (tag) - -jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USER }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - # Build api - - name: Docker meta (API) - id: meta-api - uses: docker/metadata-action@v5 - with: - images: os2display/display-api-service - - - name: Build and push (API) - uses: docker/build-push-action@v6 - with: - context: ./infrastructure/display-api-service/ - file: ./infrastructure/display-api-service/Dockerfile - build-args: | - APP_VERSION=${{ github.ref }} - push: true - tags: ${{ steps.meta-api.outputs.tags }} - labels: ${{ steps.meta-api.outputs.labels }} - provenance: mode=max - sbom: true - - # Build nginx (depends on api build) - - name: Docker meta (Nginx) - id: meta-nginx - uses: docker/metadata-action@v5 - with: - images: os2display/display-api-service-nginx - - - name: Get the tag - id: get_tag - run: echo ::set-output name=git_tag::$(echo $GITHUB_REF_NAME) - - - name: Build and push (Nginx) - uses: docker/build-push-action@v5 - with: - context: ./infrastructure/nginx/ - file: ./infrastructure/nginx/Dockerfile - build-args: | - APP_VERSION=${{ steps.get_tag.outputs.git_tag }} - push: true - tags: ${{ steps.meta-nginx.outputs.tags }} - labels: ${{ steps.meta-nginx.outputs.labels }} - provenance: mode=max - sbom: true diff --git a/.github/workflows/doctrine.yaml b/.github/workflows/doctrine.yaml index c24edb83e..dccba03b8 100644 --- a/.github/workflows/doctrine.yaml +++ b/.github/workflows/doctrine.yaml @@ -11,7 +11,7 @@ jobs: name: Validate Schema steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/github_build_release.yml b/.github/workflows/github_build_release.yml index 405dd2593..482cbdc23 100644 --- a/.github/workflows/github_build_release.yml +++ b/.github/workflows/github_build_release.yml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Task task runner - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 - name: Install, Build, Cleanup run: | diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml index 801ddbe93..4dacffe6e 100644 --- a/.github/workflows/javascript.yaml +++ b/.github/workflows/javascript.yaml @@ -27,7 +27,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - run: | docker network create frontend diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index 60fc0ee5c..066063a5c 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -34,7 +34,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - run: | docker network create frontend diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index ee9679776..02dc8c532 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -48,7 +48,7 @@ jobs: name: PHP - Check Coding Standards runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | docker network create frontend docker compose run --rm phpfpm composer install diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index a79440dd9..9daee407b 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -11,7 +11,7 @@ jobs: name: PHP Unit tests steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml index 5b78012d6..93ae20714 100644 --- a/.github/workflows/playwright.yaml +++ b/.github/workflows/playwright.yaml @@ -13,7 +13,7 @@ jobs: fail-fast: true steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend @@ -38,7 +38,7 @@ jobs: docker compose run --rm playwright npx playwright install --with-deps docker compose run --rm playwright npx playwright test - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: always() with: name: playwright-report diff --git a/.github/workflows/psalm.yaml b/.github/workflows/psalm.yaml index 65785a415..4179eae48 100644 --- a/.github/workflows/psalm.yaml +++ b/.github/workflows/psalm.yaml @@ -11,7 +11,7 @@ jobs: name: Psalm steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/rector.yaml b/.github/workflows/rector.yaml index 68ab15301..47bacbc71 100644 --- a/.github/workflows/rector.yaml +++ b/.github/workflows/rector.yaml @@ -11,7 +11,7 @@ jobs: name: Rector steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/styles.yaml b/.github/workflows/styles.yaml index edc796020..02f9d6bcd 100644 --- a/.github/workflows/styles.yaml +++ b/.github/workflows/styles.yaml @@ -27,7 +27,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - run: | docker network create frontend diff --git a/.github/workflows/twig.yaml b/.github/workflows/twig.yaml index 9dc424b45..76e2a57d6 100644 --- a/.github/workflows/twig.yaml +++ b/.github/workflows/twig.yaml @@ -40,7 +40,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - run: | docker network create frontend diff --git a/.github/workflows/vitest.yaml b/.github/workflows/vitest.yaml index 7203fe02f..bda6ac97f 100644 --- a/.github/workflows/vitest.yaml +++ b/.github/workflows/vitest.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup network run: docker network create frontend diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml index 1c0ada3f7..16c44b9ff 100644 --- a/.github/workflows/yaml.yaml +++ b/.github/workflows/yaml.yaml @@ -31,7 +31,7 @@ jobs: yaml-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: | docker network create frontend diff --git a/CHANGELOG.md b/CHANGELOG.md index d64e65aa9..ba3f7d118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ All notable changes to this project will be documented in this file. - Fixed nginx static-file location to fall back to PHP so LiipImagineBundle can generate missing thumbnails (#370). - Annotated `.env` so it serves as the canonical, self-documenting Symfony env example, with a CI check that enforces it stays in sync with `config/`. +- Switched image build pipeline to GHCR with multi-arch layer caching. ### NB! Prior to 3.x the project was split into separate repositories diff --git a/infrastructure/build-n-push.sh b/infrastructure/build-n-push.sh index 04f244043..0f64eee74 100755 --- a/infrastructure/build-n-push.sh +++ b/infrastructure/build-n-push.sh @@ -1,27 +1,45 @@ #!/bin/sh - set -eux -APP_VERSION=develop - -docker pull itkdev/php8.4-fpm:alpine -docker pull nginxinc/nginx-unprivileged:alpine - -docker build --build-context repository-root=.. \ - --platform linux/amd64,linux/arm64 \ - --pull \ - --no-cache \ - --build-arg APP_VERSION=${APP_VERSION} \ - --tag=turegjorup/display-api-service:${APP_VERSION} \ - --file="display-api-service/Dockerfile" display-api-service +APP_VERSION="${APP_VERSION:-develop}" +REGISTRY="${REGISTRY:-ghcr.io/os2display}" +RELEASE_TIMESTAMP="$(date +%s)" +# Set BUILD_LOAD=1 to build for the host platform and load into the local +# docker daemon instead of pushing. Useful for local smoke tests; production +# builds (multi-arch + registry push) require BUILD_LOAD unset or 0. +if [ "${BUILD_LOAD:-0}" = "1" ]; then + PLATFORMS="" + OUTPUT="--load" +else + PLATFORMS="--platform=linux/amd64,linux/arm64" + OUTPUT="--push" +fi -docker build --build-context repository-root=.. \ - --platform linux/amd64,linux/arm64 \ - --no-cache \ - --build-arg VERSION=${APP_VERSION} \ - --tag=turegjorup/display-api-service-nginx:${APP_VERSION} \ - --file="nginx/Dockerfile" nginx +# API (php-fpm) image. Context is the API infra dir so the Dockerfile picks +# up docker-entrypoint.sh; repository-root is provided as a named build context +# for the COPY --from=repository-root steps. +# shellcheck disable=SC2086 +docker buildx build \ + ${PLATFORMS} \ + --pull ${OUTPUT} \ + --build-context repository-root=. \ + --build-arg APP_VERSION="${APP_VERSION}" \ + --build-arg APP_RELEASE_VERSION="${APP_VERSION}" \ + --build-arg APP_RELEASE_TIMESTAMP="${RELEASE_TIMESTAMP}" \ + --tag "${REGISTRY}/display-api-service:${APP_VERSION}" \ + --file infrastructure/display-api-service/Dockerfile \ + infrastructure/display-api-service -docker push os2display/display-api-service:${APP_VERSION} -docker push os2display/display-api-service-nginx:${APP_VERSION} +# Nginx image. Context is the nginx infra dir so 'COPY etc /etc/nginx' resolves. +# shellcheck disable=SC2086 +docker buildx build \ + ${PLATFORMS} \ + --pull ${OUTPUT} \ + --build-context repository-root=. \ + --build-arg APP_VERSION="${APP_VERSION}" \ + --build-arg APP_RELEASE_VERSION="${APP_VERSION}" \ + --build-arg APP_RELEASE_TIMESTAMP="${RELEASE_TIMESTAMP}" \ + --tag "${REGISTRY}/display-api-service-nginx:${APP_VERSION}" \ + --file infrastructure/nginx/Dockerfile \ + infrastructure/nginx diff --git a/infrastructure/display-api-service/Dockerfile b/infrastructure/display-api-service/Dockerfile index 748268a43..be3e5b4b3 100644 --- a/infrastructure/display-api-service/Dockerfile +++ b/infrastructure/display-api-service/Dockerfile @@ -89,8 +89,7 @@ COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer # Download Prometheus php-fpm export. COPY --from=hipages/php-fpm_exporter:2.2.0 /php-fpm_exporter /usr/local/bin/php-fpm_exporter -COPY docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh +COPY --chmod=0755 docker-entrypoint.sh /usr/local/bin/ USER deploy diff --git a/infrastructure/nginx/Dockerfile b/infrastructure/nginx/Dockerfile index 5bf5c00f7..eb3cb3a17 100644 --- a/infrastructure/nginx/Dockerfile +++ b/infrastructure/nginx/Dockerfile @@ -76,16 +76,13 @@ ENV APP_CLIENT_PATH=/app \ WORKDIR ${APP_API_PATH} -# Create directory and copy built client assets -RUN mkdir -p ${APP_API_PATH}/public -COPY --from=api_app_builder --chown=$UID:0 ${APP_API_PATH}/public ./public +# Copy built client assets with permissions baked in (avoids a duplicate +# layer that a separate `RUN chmod -R` would produce — saves ~19 MB). +COPY --from=api_app_builder --chown=$UID:0 --chmod=775 ${APP_API_PATH}/public ./public -# Copy configuration and entrypoint script +# Copy nginx configuration COPY --chown=$UID:0 etc /etc/nginx -# Set proper permissions (files are already owned by $UID:0 from COPY --chown) -RUN chmod -R g+w ${APP_API_PATH} - # Add health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:${NGINX_PORT}/health || exit 1