Skip to content

Conversation

@dkd-dobberkau
Copy link

@dkd-dobberkau dkd-dobberkau commented Feb 9, 2026

Summary

  • Add linux/arm64 support for all 4 Docker images (generator, app, test, dev), enabling native execution on Apple Silicon and ARM servers (AWS Graviton) without QEMU emulation
  • Rewrite CI workflow to use Docker Buildx push-by-digest pattern with native ARM runners (ubuntu-24.04-arm)
  • Make Dockerfile-test arch-aware: Google Chrome + ChromeDriver on amd64, Chromium + chromium-driver on arm64; bump dockerize to v0.8.0 for arm64 support

Closes #99

Changes

Dockerfile-test

  • Use TARGETARCH build arg (auto-set by buildx) for conditional installs:
    • amd64: Google Chrome from Google's apt repo + matching ChromeDriver from Chrome for Testing
    • arm64: chromium + chromium-driver from Debian repos (Google Chrome has no arm64 Linux build)
  • Bump dockerize from v0.6.1 to v0.8.0 (first version with arm64 binaries)

.github/workflows/dockerhub.yml

  • Replace monolithic single-job workflow with 9-job pipeline:
    • prepare fetches Decidim/Ruby/Node versions
    • build-{image} matrix [linux/amd64, linux/arm64] with native runners
    • merge-{image} combines per-arch digests into multi-arch manifests
  • Dependency chain: generator then (app + test in parallel) then dev
  • Push to both Docker Hub and GHCR (preserves existing behavior)
  • Add GHA buildx layer cache to avoid rebuilding layers for dual-registry push
  • Update all Actions to latest versions
  • Fix deprecated set-output command
  • Tags: latest, version, sha (unchanged)

docker-compose.yml

  • No functional changes (reverted to upstream state)

Motivation

Issue #99 has been open since Dec 2022. On Apple Silicon Macs and ARM servers, the current amd64-only images run via slow QEMU emulation. Comparable projects (Discourse, Mastodon, GitLab, Chatwoot) all ship multi-arch images. This PR brings Decidim in line.

Test plan

  • Verify workflow YAML parses correctly
  • CI builds both architectures for all 4 images
  • After merge, verify multi-arch manifests
  • Pull and run on Apple Silicon Mac without --platform flag
  • Pull and run on amd64 machine with no behavior change

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added multi-architecture Docker image support, including ARM64 builds alongside x86_64.
  • Chores

    • Refactored CI/CD pipeline for enhanced multi-platform Docker image building and publishing.
    • Updated architecture detection in Docker images for improved cross-platform compatibility.

Replace the single-arch CI workflow with a multi-arch build pipeline
using Docker Buildx push-by-digest pattern and native ARM runners.
This enables Decidim Docker images to run natively on Apple Silicon
and ARM servers (e.g. AWS Graviton) without slow QEMU emulation.

- Dockerfile-test: Make Chrome, ChromeDriver, and dockerize arch-aware
  via TARGETARCH (Google Chrome on amd64, Chromium on arm64; bump
  dockerize to v0.8.0 for arm64 support)
- Workflow: Split into prepare/build/merge jobs with matrix strategy,
  native ubuntu-24.04-arm runners, GHA buildx cache, dual registry
  push (Docker Hub + GHCR)
- docker-compose.yml: Revert to upstream state

Closes decidim#99

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

The CI/CD workflow and Dockerfiles have been redesigned to support multi-architecture image builds (linux/amd64 and linux/arm64). The workflow now uses per-architecture builders with manifest merging stages, and Dockerfile-test implements architecture-aware tooling setup. Minor formatting updates were applied to the compose file.

Changes

Cohort / File(s) Summary
CI/CD Pipeline Restructuring
.github/workflows/dockerhub.yml
Replaced monolithic build-publish job with multi-stage pipeline: prepare stage extracts versions, per-arch builders for generator/app/test/dev images handle linux/amd64 and linux/arm64, manifest merge stages assemble multi-arch manifests from per-arch digests, artifact flows transfer digests between stages. Dual-pipeline publishing to Docker Hub and GHCR maintained.
Architecture-Aware Test Image
Dockerfile-test
Introduced TARGETARCH ARG for architecture-aware conditional logic. arm64 installs chromium/chromium-driver; non-arm64 uses google-chrome-stable with dynamic chromedriver. Consolidated arch-aware dockerize v0.8.0 installation based on TARGETARCH. Consolidated setup in single RUN blocks.
Configuration Formatting
docker-compose.yml
Removed trailing spaces in comment lines under decidim service. No functional changes to entrypoint, command, or runtime behavior.

Sequence Diagram(s)

sequenceDiagram
    participant GH as GitHub Actions
    participant Prep as Prepare Stage
    participant BA as Build-Arch (Gen/App/Test/Dev)
    participant Reg as Registries<br/>(Hub/GHCR)
    participant Merge as Manifest<br/>Merge Stage
    
    GH->>Prep: Trigger workflow
    Prep->>Prep: Extract decidim, ruby,<br/>node versions
    Prep-->>BA: Output versions
    
    BA->>BA: Build image for<br/>linux/amd64
    BA->>Reg: Push amd64 image
    Reg-->>BA: Return digest
    BA->>BA: Build image for<br/>linux/arm64
    BA->>Reg: Push arm64 image
    Reg-->>BA: Return digest
    BA-->>GH: Upload digests<br/>as artifacts
    
    GH->>Merge: Download digests
    Merge->>Reg: Login to registries
    Merge->>Reg: Create manifest from<br/>amd64 + arm64 digests
    Reg-->>Merge: Multi-arch manifest
    Merge->>Reg: Push manifest
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Poem

🐰 A rabbit hops through build stages new,
From amd64 to arm64 too!
Manifests merge in Docker's grand hall,
One image for devices, both great and small!
Silicon apples can now take a bite, 🍎
Of decidim, running just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the main change: adding multi-architecture Docker builds for both amd64 and arm64 platforms.
Linked Issues check ✅ Passed The PR successfully implements the requirement from issue #99 to provide native arm64 Docker images, enabling Decidim to run on Apple Silicon without QEMU emulation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to multi-architecture Docker support: workflow restructuring for dual-platform builds, Dockerfile-test arch-awareness, and trivial comment formatting in docker-compose.yml.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/dockerhub.yml (2)

37-51: ⚠️ Potential issue | 🟠 Major

curl calls lack failure detection — empty versions will silently propagate.

If the curl to fetch .ruby-version or .node-version fails (e.g., tag doesn't exist, network issue), the version output will be empty, and downstream builds will receive empty build-args. Use curl -sf or curl --fail so the step fails fast instead of silently producing broken images.

🛡️ Proposed fix
-      run: echo "version=$(curl -s $RUBY_VERSION_URL)" >> $GITHUB_OUTPUT
+      run: echo "version=$(curl -sf "$RUBY_VERSION_URL")" >> $GITHUB_OUTPUT
-      run: echo "version=$(curl -s $NODE_VERSION_URL | cut -d. -f1)" >> $GITHUB_OUTPUT
+      run: echo "version=$(curl -sf "$NODE_VERSION_URL" | cut -d. -f1)" >> $GITHUB_OUTPUT

30-35: ⚠️ Potential issue | 🟡 Minor

Pin third-party action to a full commit SHA.

oprypin/find-latest-tag@v1 uses a mutable tag that can be retargeted without notice. Pin to a specific commit SHA to prevent supply-chain attacks. The current v1.1.2 release points to dd2729fe78b0bb55523ae2b2a310c6773a652bd1:

uses: oprypin/find-latest-tag@dd2729fe78b0bb55523ae2b2a310c6773a652bd1 # v1.1.2
🤖 Fix all issues with AI agents
In @.github/workflows/dockerhub.yml:
- Around line 43-45: The current GitHub Actions step "Set Decidim Version" (id:
decidim-version) is injecting `${{ steps.decidim-tag.outputs.tag }}` directly
into the shell `run:` which risks shell interpolation; change the step to pass
the tag via an environment variable and reference that env var in the `run:`
instead: set an env entry (e.g., DECIDIM_TAG: ${{ steps.decidim-tag.outputs.tag
}}) and update the shell command to use the safe env variable (e.g., echo
"version=$(echo $DECIDIM_TAG | cut -c2-)" >> $GITHUB_OUTPUT) so the value is not
expanded by the Action runner into the shell before execution. Ensure the step
keeps the id decidim-version and the output writing to $GITHUB_OUTPUT.
- Around line 63-64: Remove the dead job output declaration that exports
image_name using ${{ env.GENERATOR_IMAGE_NAME }} because the env context isn't
available at the job outputs level and the output is unused; delete the outputs:
image_name line (and its value reference to GENERATOR_IMAGE_NAME) from the job
definition so there is no misleading/empty build-generator.outputs.image_name
export.
🧹 Nitpick comments (2)
Dockerfile-test (1)

9-27: Arch-aware browser installation looks correct overall; a few minor observations.

  1. apt-key add - (Line 15) is deprecated in modern Debian/Ubuntu. Consider using a signed-by keyring approach instead. This is non-urgent since it still works, but apt-key may be removed in future base images.

  2. Neither branch removes /var/lib/apt/lists/* after apt-get clean, which inflates the layer size. Consider appending && rm -rf /var/lib/apt/lists/* to both branches.

♻️ Suggested improvement for apt cleanup
 RUN if [ "$TARGETARCH" = "arm64" ]; then \
       apt-get update \
       && apt-get install -y chromium chromium-driver \
-      && apt-get clean; \
+      && apt-get clean \
+      && rm -rf /var/lib/apt/lists/*; \
     else \
       wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
       && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
       && apt-get update \
       && apt-get install -y google-chrome-stable \
-      && apt-get clean \
       && apt-get install -y unzip curl \
       && CHROME_VERSION=$(google-chrome --version | grep -oP '\d+\.\d+\.\d+\.\d+') \
       && CHROMEDRIVER_URL="https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chromedriver-linux64.zip" \
       && curl -sL -o /tmp/chromedriver.zip "$CHROMEDRIVER_URL" \
       && unzip -j /tmp/chromedriver.zip '*/chromedriver' -d /usr/local/bin \
       && rm /tmp/chromedriver.zip \
-      && apt-get clean; \
+      && apt-get clean \
+      && rm -rf /var/lib/apt/lists/*; \
     fi
.github/workflows/dockerhub.yml (1)

53-627: Consider extracting reusable workflows to reduce ~600 lines of duplication.

The four build jobs and four merge jobs are structurally identical, differing only in image name, Dockerfile, build-args, and needs. A pair of reusable workflows (build-image.yml and merge-manifest.yml with workflow_call) parameterized by these values would cut the workflow to ~100 lines and make maintenance far easier.

This isn't blocking, but the current duplication means every future change (e.g., adding a registry, changing cache config, adding a build step) must be replicated in four places.

Comment on lines 43 to +45
- name: Set Decidim Version
id: decidim-version
run: echo ::set-output name=version::$(echo ${{ steps.decidim-tag.outputs.tag }} | cut -c2-)
run: echo "version=$(echo ${{ steps.decidim-tag.outputs.tag }} | cut -c2-)" >> $GITHUB_OUTPUT
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential script injection via ${{ }} interpolation in run:.

${{ steps.decidim-tag.outputs.tag }} is interpolated directly into the shell command. If the tag ever contains shell metacharacters (e.g., $(cmd), backticks, ;), they would be executed. While release tags for decidim/decidim are under your control, the safer pattern is to pass the value through an environment variable:

🛡️ Proposed fix
     - name: Set Decidim Version
       id: decidim-version
-      run: echo "version=$(echo ${{ steps.decidim-tag.outputs.tag }} | cut -c2-)" >> $GITHUB_OUTPUT
+      env:
+        TAG: ${{ steps.decidim-tag.outputs.tag }}
+      run: echo "version=$(echo "$TAG" | cut -c2-)" >> $GITHUB_OUTPUT
🤖 Prompt for AI Agents
In @.github/workflows/dockerhub.yml around lines 43 - 45, The current GitHub
Actions step "Set Decidim Version" (id: decidim-version) is injecting `${{
steps.decidim-tag.outputs.tag }}` directly into the shell `run:` which risks
shell interpolation; change the step to pass the tag via an environment variable
and reference that env var in the `run:` instead: set an env entry (e.g.,
DECIDIM_TAG: ${{ steps.decidim-tag.outputs.tag }}) and update the shell command
to use the safe env variable (e.g., echo "version=$(echo $DECIDIM_TAG | cut
-c2-)" >> $GITHUB_OUTPUT) so the value is not expanded by the Action runner into
the shell before execution. Ensure the step keeps the id decidim-version and the
output writing to $GITHUB_OUTPUT.

Comment on lines +63 to +64
outputs:
image_name: ${{ env.GENERATOR_IMAGE_NAME }}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dead output — env context is unavailable at job outputs level.

${{ env.GENERATOR_IMAGE_NAME }} in a job-level outputs expression evaluates to an empty string because env is only available inside steps. Moreover, no downstream job references build-generator.outputs.image_name. Remove these lines to avoid confusion.

🧹 Proposed fix
     runs-on: ${{ contains(matrix.platform, 'arm64') && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
-    outputs:
-      image_name: ${{ env.GENERATOR_IMAGE_NAME }}
     steps:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
outputs:
image_name: ${{ env.GENERATOR_IMAGE_NAME }}
runs-on: ${{ contains(matrix.platform, 'arm64') && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
steps:
🤖 Prompt for AI Agents
In @.github/workflows/dockerhub.yml around lines 63 - 64, Remove the dead job
output declaration that exports image_name using ${{ env.GENERATOR_IMAGE_NAME }}
because the env context isn't available at the job outputs level and the output
is unused; delete the outputs: image_name line (and its value reference to
GENERATOR_IMAGE_NAME) from the job definition so there is no misleading/empty
build-generator.outputs.image_name export.

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.

arm64 image?

1 participant