diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9df46a6..9e6f0ed 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -14,6 +14,7 @@ name: Validate DevContainer
description: This workflow is checking that updates do not break stuff. If on main branch, publish to "latest" tag.
on:
pull_request:
+ -types: [opened, synchronize, reopened, labeled]
push:
branches:
- main
@@ -71,6 +72,13 @@ jobs:
# Test
./scripts/test.sh
+ # Test consumer repository, arm64 is skipped because not all bazel dependencies are available for arm64
+ # By default this is skipped in pull requests, unless the label "test-consumer" is added
+ if [ "${{ matrix.os }}" = "amd64" ] && ( [ "${{ github.event_name }}" != "pull_request" ] || echo "${{ toJSON(github.event.pull_request.labels) }}" | grep -q "test-consumer" ); then
+ ./scripts/test_consumer.sh "https://github.com/eclipse-score/inc_someip_gateway.git" "14a733a1f3d5f6fa76ac13365d57c8df30b62a86"
+ ./scripts/test_consumer.sh "https://github.com/eclipse-score/score.git" "7cbb86d54dced80e33a6405bc92f8abe62200ad4"
+ fi
+
# Optionally: Publish
# We do not use the push feature of devcontainers/ci here, since that would push the wrong container.
# Instead, we use the publish script which pushes the correct container (residing in src/s-core-devcontainer).
diff --git a/README.md b/README.md
index a06ae29..3584f0c 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ SPDX-License-Identifier: Apache-2.0
This repository contains the common [development container](https://containers.dev) for [Eclipse S-CORE](https://github.com/eclipse-score).
It contains all tools required to develop (modify, build, ...) Eclipse S-CORE.
All tool version are well-defined, and all tools are pre-configured to work as expected for Eclipse S-CORE development.
-The container is [pre-built](https://containers.dev/guide/prebuild) in GitHub Actions as part of this repository, tested, published, and ready for use.
+The container is [pre-built](https://containers.dev/guide/prebuild) in GitHub Actions as part of this repository, [tested, published](./docs/ci.md), and ready for use.
Using the pre-built container in an Eclipse S-CORE repository is described in the [Usage](#usage) section.
@@ -312,6 +312,10 @@ The Visual Studio Code instance related to the targeted S-CORE module will now a
If not, press Ctrl + Shift + p and run from there "Dev Containers: Rebuilt Container Without Cache".
Do so, and you have a running instance of `S-CORE DevContainer` related to the targeted S-CORE module.
+### Consumer tests
+
+To enable consumer tests in the pull request, read the [ci.md](./docs/ci.md) document.
+
### Version Pinning
The `S-CORE DevContainer` pins feature and tool versions.
diff --git a/docs/README.md b/docs/README.md
index bb91971..20f8a09 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -98,14 +98,3 @@ It strengthens the supply chain by pinning versions and hashes, sourcing feature
Pre-built images have a higer availability than the set of all tools which are installed (one download from a location controlled by S-CORE vs. many downloads from "everywhere").
Pre-built images can be easily archived anywhere, e.g. for reproducibility of builds in real production use-cases.
It also enforces a clear separation of concerns: general tooling is delivered through reusable features, S-CORE–specific logic lives in a dedicated feature, and image composition plus publishing are centralized.
-
-## Release Automation
-
-Releases are cut automatically once per week from `main`, but only if commits were added since the latest `v..` tag.
-The scheduled workflow creates the git tag and GitHub release, and the existing tag-triggered release workflow then builds, tests and publishes the matching container image.
-
-The next semantic version is derived from commit messages since the previous release:
-
-* breaking changes (`!` in the conventional commit header or `BREAKING CHANGE:` in the body) increment the major version
-* `feat` commits increment the minor version
-* every other commit increments the patch version so maintenance-only weeks still publish a new immutable image
diff --git a/docs/ci.md b/docs/ci.md
new file mode 100644
index 0000000..29a5615
--- /dev/null
+++ b/docs/ci.md
@@ -0,0 +1,55 @@
+
+
+# Continuous Integration
+
+The CI workflow (`.github/workflows/ci.yaml`) validates the devcontainer on every pull request, push to `main`, and merge group event.
+
+Builds run on two architectures in parallel:
+
+- **AMD64**
+- **ARM64**
+
+The S-CORE devcontainer is build using this repo's `.devcontainer` to ensure consistent behavior at developer and CI.
+
+## Merge Job (main only)
+
+Each architecture is built and tested individually.
+After both architecture builds succeed, a merge job creates a multi-arch manifest and pushes it to `ghcr.io`.
+It would have been ideal if the multi-arch image could have been build in one go, but that did not work.
+
+## Consumer Tests
+
+At consumer tests, repos using the devcontainer are build and tested using the newly created devcontainer image.
+This is done to detect breaking changes before releasing a new devcontainer image and to reduce manual testing efforts.
+
+By default these are not run in pull requests, but in the merge queue and pushes to main to save time.
+If these shall be run in the pull request add the **`test-consumer`** label to a pull request.
+
+> [!NOTE]
+> Github may not trigger a new workflow run after the label is set.
+> You need to push another commit for that.
+
+## Release Automation
+
+Releases are cut automatically once per week from `main`, but only if commits were added since the latest `v..` tag.
+The scheduled workflow creates the git tag and GitHub release, and the existing tag-triggered release workflow then builds, tests and publishes the matching container image.
+
+The next semantic version is derived from commit messages since the previous release:
+
+* breaking changes (`!` in the conventional commit header or `BREAKING CHANGE:` in the body) increment the major version
+* `feat` commits increment the minor version
+* every other commit increments the patch version so maintenance-only weeks still publish a new immutable image
diff --git a/scripts/test_consumer.sh b/scripts/test_consumer.sh
new file mode 100755
index 0000000..7006ea2
--- /dev/null
+++ b/scripts/test_consumer.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+# *******************************************************************************
+# Copyright (c) 2026 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# SPDX-License-Identifier: Apache-2.0
+# *******************************************************************************
+
+set -euxo pipefail
+
+# Usage: test_consumer.sh [revision] [devcontainer-image]
+# Tests that a consumer repository can be built and tested using the devcontainer.
+# It is checked that these commands work without errors:
+# - bazel build //...
+# - bazel test //...
+# Parameters:
+# repo-url : Git URL of the consumer repository
+# revision : Git branch/tag/commit (default: main)
+
+REPO_URL="${1:?Repository URL is required}"
+REVISION="${2:-main}"
+
+IMAGE="s-core-devcontainer"
+
+export DOCKER_BUILDKIT=1
+
+SCRIPT_PATH=$(readlink -f "$0")
+SCRIPT_DIR=$(dirname -- "${SCRIPT_PATH}")
+PROJECT_DIR=$(dirname -- "${SCRIPT_DIR}")
+ID_LABEL="test-container=${IMAGE}"
+
+. "${SCRIPT_DIR}/functions.sh"
+set_dockerfile_name
+
+devcontainer up \
+ --id-label "${ID_LABEL}" \
+ --workspace-folder "${PROJECT_DIR}/src/${IMAGE}/" \
+ --remove-existing-container
+
+# Extract repo name from URL
+REPO_NAME=$(basename "${REPO_URL}" .git)
+REPO_WORKSPACE="/tmp/${REPO_NAME}"
+
+echo "(*) Cloning repository..."
+# --revision not supported by older git versions, so we clone first and then checkout the revision
+# devcontainer exec --id-label "${ID_LABEL}" git clone --depth 1 --revision "${REVISION}" "${REPO_URL}" "${REPO_WORKSPACE}"
+devcontainer exec --id-label "${ID_LABEL}" git clone "${REPO_URL}" "${REPO_WORKSPACE}"
+devcontainer exec --id-label "${ID_LABEL}" git -C "${REPO_WORKSPACE}" checkout "${REVISION}"
+
+# Run build and test with Bazel using docker exec
+echo "(*) Running Bazel build in devcontainer..."
+devcontainer exec --id-label="${ID_LABEL}" /bin/sh -c "set -e && cd \"${REPO_WORKSPACE}\" && bazel build //..."
+
+echo "(*) Running Bazel test in devcontainer..."
+devcontainer exec --id-label="${ID_LABEL}" /bin/sh -c "set -e && cd \"${REPO_WORKSPACE}\" && bazel test //..."
+
+echo "(*) Bazel build and test completed successfully for ${REPO_NAME}"