Skip to content

Conversation

BacLuc
Copy link
Contributor

@BacLuc BacLuc commented Aug 2, 2025

CI: parallelize docker build

And simplify adding new image.
Now you just have to add a docker compose with an image name and a build section.

Also unset the image of api for dev, some people had problems when
there was an image defined which cannot be downloaded.

Populate the buildArgValues in the build-and-push step
because passing secrets between jobs seems to be discouraged by github.
https://github.com/orgs/community/discussions/37942


CI: only build and push images if they changed

Else just retag and push the existing image.

Deployment can be seen here:

@BacLuc BacLuc requested a review from a team August 2, 2025 10:14
@BacLuc
Copy link
Contributor Author

BacLuc commented Aug 9, 2025

@manuelmeister manuelmeister requested a review from Copilot August 11, 2025 20:33
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes Docker builds in CI by implementing parallel builds and skipping builds for unchanged images. It introduces a new architecture that automatically discovers buildable images from Docker Compose files and only builds them if the relevant files have changed, otherwise retagging existing images.

  • Parallelized Docker image builds using a matrix strategy
  • Added logic to skip builds for unchanged images by checking git history and existing image tags
  • Simplified adding new images by using Docker Compose configuration discovery

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docker-compose.yml Updated API image reference to use configurable registry and repository variables
docker-compose.override.yml Unset image for development to avoid download issues
.github/workflows/reusable-e2e-tests-build.yml Updated API image tag format for consistency
.github/workflows/reusable-build-and-push.yml Complete rewrite to support parallel builds and conditional building based on file changes
.docker-hub/docker-compose.yml New file defining build configurations for frontend, print, and varnish images

@manuelmeister
Copy link
Member

@CodeRabbit review this

Copy link

coderabbitai bot commented Aug 11, 2025

@manuelmeister I'll review the PR changes for optimizing the Docker build process.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Aug 11, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added docker-compose config to build and tag frontend, print, and varnish images with environment-driven tags and optional Sentry build args.
    • Made API image/tag in docker-compose.yml dynamic via environment variables.
    • docker-compose override allows local API builds by clearing the image reference.
  • Chores

    • Reworked build-and-push workflow to a matrix-based pipeline with commit-aware tagging, merged build args, caching, and retagging; enabled manual runs and BuildKit.
    • Updated E2E workflow to use docker.io/ecamp/ecamp3-api:latest for the API image.

Walkthrough

Adds a Docker Hub-oriented docker-compose file for building three images. Overhauls the reusable build-and-push GitHub workflow to a matrix-based, tag-aware pipeline with a discovery step. Updates API image references to parameterized registry/owner/tag in compose and adjusts E2E workflow to use docker.io/ecamp/ecamp3-api:latest.

Changes

Cohort / File(s) Summary
Docker Hub compose definitions
.docker-hub/docker-compose.yml
New compose file defining build contexts, Dockerfiles, tags, and Sentry build args for frontend and print; varnish defined without build args. Uses env-driven REGISTRY/REPO_OWNER/VERSION for tagging.
CI workflow: Build & push overhaul
.github/workflows/reusable-build-and-push.yml
Rewritten to matrix-driven builds with a discovery (build-info) job, dynamic tag computation (commit-aware), build-args merging, expanded tags, conditional retagging, and per-image caching. Adds workflow_dispatch and global Docker build env.
CI workflow: E2E image tag change
.github/workflows/reusable-e2e-tests-build.yml
Switches API image reference to docker.io/ecamp/ecamp3-api:latest in the build step; tar output unchanged.
Runtime compose: API image parameterization
docker-compose.yml, docker-compose.override.yml
docker-compose.yml: API image now templated via REGISTRY/REPO_OWNER/VERSION defaults. docker-compose.override.yml: adds empty image field to api service to prefer local build override.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Reusable Workflow Caller
  participant GH as GitHub Actions
  participant BI as Job: build-info
  participant BP as Job: build-and-push (matrix)
  participant Reg as Docker Registry

  Caller->>GH: Trigger workflow (push/workflow_dispatch)
  GH->>BI: Run discovery
  BI-->>GH: Outputs build-config[], repo-owner, tags

  loop per image in build-config (matrix)
    GH->>BP: Start job with matrix.build-config
    BP->>Reg: Check existing tags / latest commit scope
    BP->>BP: Compute/expand tags + build-args
    BP->>Reg: Build & push image with tags
    alt image existed
      BP->>Reg: Retag and push derived tags
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6eac6af and 4cfcacc.

📒 Files selected for processing (5)
  • .docker-hub/docker-compose.yml (1 hunks)
  • .github/workflows/reusable-build-and-push.yml (2 hunks)
  • .github/workflows/reusable-e2e-tests-build.yml (1 hunks)
  • docker-compose.override.yml (1 hunks)
  • docker-compose.yml (1 hunks)
🔇 Additional comments (11)
docker-compose.yml (1)

28-28: LGTM! Dynamic image reference improves flexibility.

The parameterized image path using environment variables with sensible defaults enables flexible deployment across different registries and environments while maintaining backward compatibility.

docker-compose.override.yml (1)

3-3: LGTM! Empty image field prevents unwanted image pulls during development.

Setting image: '' in the override file effectively disables the dynamic image reference from the main compose file, ensuring developers use locally built images instead of potentially pulling from the registry.

.github/workflows/reusable-e2e-tests-build.yml (1)

28-28: Confirm Tarball Filename in Consuming Workflows

I ran a search for /tmp/ecamp3-dev-api.tar across the repo and found no references. Please verify that any downstream or external workflows still expect this filename—or update it to match the new image naming convention (ecamp3-api.tar or similar).

.docker-hub/docker-compose.yml (3)

1-12: LGTM! Well-structured frontend service configuration.

The service definition follows best practices with dynamic image naming and clearly documented build arguments. The comment about GitHub secrets registration is particularly helpful for maintainers.


13-23: LGTM! Consistent print service configuration.

The print service configuration mirrors the frontend service structure appropriately, with service-specific build arguments for Sentry integration.


24-28: LGTM! Simple and clean varnish service configuration.

The varnish service appropriately has no build arguments, keeping the configuration minimal and focused.

.github/workflows/reusable-build-and-push.yml (5)

4-4: LGTM! Workflow dispatch enables manual testing.

Adding workflow_dispatch is useful for manual testing and debugging of the build process.


21-23: LGTM! Docker environment variables improve build performance.

Setting DOCKER_BUILDKIT=1 and COMPOSE_DOCKER_CLI_BUILD=1 enables modern Docker features and improved build performance.


26-41: LGTM! Dynamic repository owner detection is well-implemented.

The GitHub script approach to get the lowercase repository owner is clean and handles the GitHub requirement for lowercase image names properly.


115-119: LGTM! Matrix strategy enables scalable multi-service builds.

The matrix-driven approach using the dynamically discovered build configuration is a significant improvement over the previous fixed per-service approach. This makes adding new services much easier.


252-258: LGTM! Retagging logic optimizes CI performance.

The retagging approach when images already exist is an excellent optimization that avoids unnecessary rebuilds while ensuring all required tags are available.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch optimize-docker-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@BacLuc BacLuc requested review from a team and removed request for a team August 11, 2025 22:05
And simplify adding new image.
Now you just have to add a docker compose with an image name and a build section.

Also unset the image of api for dev, some people had problems when
there was an image defined which cannot be downloaded.

Populate the buildArgValues in the build-and-push step
because passing secrets between jobs seems to be discouraged by github.
https://github.com/orgs/community/discussions/37942
Copy link
Member

@carlobeltrame carlobeltrame left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as far as I understand, this essentially replaces the explicit workflow yml for each image build with a single matrix build which builds the images. I see how this reduces code duplication (however, this PR includes 265 added lines and 71 deleted lines, so our code actually got larger...)
Assuming this refactoring towards DRY is good for maintainability. But how is this faster than before? Were the image builds sequential steps before? Why couldn't we just convert the workflow steps to workflow jobs?

Comment on lines +213 to +217
"SENTRY_AUTH_TOKEN" : "${{ secrets.SENTRY_AUTH_TOKEN }}",
"SENTRY_FRONTEND_PROJECT" : "${{ vars.SENTRY_FRONTEND_PROJECT }}",
"SENTRY_ORG" : "${{ vars.SENTRY_ORG }}",
"SENTRY_PRINT_PROJECT" : "${{ vars.SENTRY_PRINT_PROJECT }}",
"SENTRY_RELEASE_NAME" : "${{ inputs.sha }}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change in this PR, but can you explain why the sentry settings are build args instead of environment variables? This way they're baked into the images, right? Why is that beneficial for the sentry settings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was too lazy until now to split sentry release bundle generation and docker build.

Comment on lines +221 to +225
for (const arg in args) {
if (buildArgValues[arg]) {
args[arg] = buildArgValues[arg]
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have an override mechanism for these? As far as I can tell, the variable values should be populated in the new docker-compose.yml already, no? Why overwrite them again here with the same values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really an override, its more filling in the build-args from the secret.

Comment on lines +2 to +28
frontend-image:
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-frontend:${VERSION:-latest}
build:
context: ../
dockerfile: .docker-hub/frontend/Dockerfile
# they have to be registered in GitHub under exactly this name in secrets or vars
args:
SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN:-}
SENTRY_ORG: ${SENTRY_ORG:-}
SENTRY_FRONTEND_PROJECT: ${SENTRY_FRONTEND_PROJECT:-}
SENTRY_RELEASE_NAME: ${RELEASE_NAME:-}
print-image:
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-print:${VERSION:-latest}
build:
context: ../
dockerfile: .docker-hub/print/Dockerfile
# they have to be registered in GitHub under exactly this name in secrets or vars
args:
SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN:-}
SENTRY_ORG: ${SENTRY_ORG:-}
SENTRY_PRINT_PROJECT: ${SENTRY_PRINT_PROJECT:-}
SENTRY_RELEASE_NAME: ${RELEASE_NAME:-}
varnish-image:
image: ${REGISTRY:-docker.io}/${REPO_OWNER:-ecamp}/ecamp3-varnish:${VERSION:-latest}
build:
context: ../
dockerfile: .docker-hub/varnish/Dockerfile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened to the api and the db-backup-restore images which we were building before?

@BacLuc BacLuc force-pushed the optimize-docker-build branch from 167b2f3 to 96c3a34 Compare September 2, 2025 21:48
@BacLuc BacLuc enabled auto-merge September 2, 2025 21:53
@BacLuc BacLuc added this pull request to the merge queue Sep 2, 2025
Merged via the queue into devel with commit 1bda9e2 Sep 2, 2025
54 checks passed
@BacLuc BacLuc deleted the optimize-docker-build branch September 2, 2025 21:57
@carlobeltrame
Copy link
Member

carlobeltrame commented Sep 3, 2025

Here is my summary of what this PR does, for later reference. Maybe we could improve the documentation about this in the Wiki, maybe even with some screenshots or diagrams, so this isn't such a big black box if you haven't read the code.

Previously, we had a GitHub Workflow which had explicit steps for each image which we build (frontend, api, print, varnish, etc.). Each of these build steps would call the docker/build-push-action with different parameters suited to the image to be built. This could be viewed as a kind of code duplication (the parameter values were different for each image, but the same docker/build-and-push-action was mentioned multiple times in the workflow file).
These image build steps were all running in sequence in the action, one after the other, and every image was rebuilt and re-tagged for every code change (with some caching).

This PR introduces a matrix build, meaning it collects the parameters for building all the images in a JSON array, and then runs a parallel GitHub Actions Job for building each image. So most important of all, the builds are parallelized now.
Collecting the parameters for the image builds works by finding all docker-compose.yml files in the repo (using the find . -name docker-compose.yml command), merging their contents, parsing, filtering and converting the required settings into a JSON array of objects, which is called the build-info or build-config inside the workflow. This potentially allows us to add new images without ever touching the GitHub Workflow code, just by adding them to any docker-compose.yml file.
We also now have a different way to specify which changed project files will necessitate a rebuild of which images, via the context option in the docker-compose.yml files. Before building any of the images, we now have a more aggressive check to skip an image build if nothing about it has changed. For now, the context setting of all images is set to the whole repo, so we don't really benefit from this mechanism yet.

So in short, the benefits are parallelization, potentially better skipping of image builds and the declarative approach "any image specified in any docker-compose.yml file in the repo will be built in this single centralized build action".
Drawbacks are that even though the code is more DRY now, it has become quite a bit longer. I have expressed my sentiment that, while this may be a beautiful piece of engineering, it makes understanding the whole workflow significantly harder for me, and I especially would not have implemented the "find all docker-compose.yml files in the repo" part the same way if I were the one doing this (I'd prefer explicitly listing the involved docker-compose.yml files used for the builds). Also, manually parsing the docker compose syntax inside GitHub Actions seems very complicated to me. I personally see a limit to the benefits of DRY over explicitness, when it concerns understandability of the code for beginners. And while I see the hassle of working with the GitHub Actions syntax, I am not sure it is worth re-implementing parts of the docker compose parser in GitHub Actions, just to avoid having to work with that syntax when we add some new service (which happens very rarely anyways IMO).

I approved the PR because @BacLuc is the main driver of our deployment pipeline, and his work getting easier is important. But I also expressed reluctance to accept more abstraction complexity in this area in the future. I think it is very important that multiple members of the core team carry the knowledge of how exactly our deployment works.

Future improvements which we discussed:

  • Make the build context for each image smaller, so we can skip rebuilding the API when only the frontend has changed etc.
  • Extract the sentry build args and report the release number to sentry via an external call, not inside the dockerfiles.
  • If in the future we ever add docker-compose.yml files which are not intended for building images, we might have to rethink or refine the "find all docker-compose.yml files in the whole repo" approach.
  • Maybe in the future the docker/build-push-action will support building from a docker-compose.yml file, or the docker compose CLI on GitHub Actions Runners will become faster, in which case we could simplify all the parsing logic for the docker-compose.yml format which had to be implemented here.

steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ inputs.sha }}
fetch-depth: 100
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants