Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 99 additions & 25 deletions .github/workflows/build-next.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (C) 2023-2024 Red Hat, Inc.
# Copyright (C) 2023-2025 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -23,37 +23,111 @@ on:
- 'main'

jobs:
build:
runs-on: ubuntu-22.04
check-builder-changes:
runs-on: ubuntu-24.04
outputs:
builder_required: ${{ steps.check.outputs.builder_required }}
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 2 # Ensure we have at least one previous commit for diff check

- name: Check for builder-related changes
id: check
run: |
if git diff --name-only HEAD^ HEAD | grep -E '^(package.json|pnpm-lock.yaml|build/Containerfile.builder|.github/workflows/build-next.yaml)$'; then
echo "builder_required=true" >> $GITHUB_OUTPUT
else
echo "builder_required=false" >> $GITHUB_OUTPUT
fi
Comment on lines +36 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit septic, are we sure we will not in a near future have this not covering all cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, but following the pattern from Apple and RHEL extensions. So I'd prefer to defer due to 'existing pattern' and I think we need a 'build epic' for future sprint where someone would look at multi-stage build, sharing workflow jobs, etc.


builder-image:
needs: check-builder-changes
if: needs.check-builder-changes.outputs.builder_required == 'true'
name: Build and publish builder OCI images only if pnpm-lock.yaml or package.json changes
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
name: Install pnpm
- name: build builder image
id: builder-image
uses: redhat-actions/buildah-build@v2
with:
run_install: false
image: extension-bootc-builder
tags: next ${{ github.sha }}
platforms: linux/amd64, linux/arm64
containerfiles: |
build/Containerfile.builder
context: .
oci: true

- uses: actions/setup-node@v6
- name: Log in to ghcr.io
uses: redhat-actions/podman-login@v1
with:
node-version: 22
cache: 'pnpm'
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io

- name: Execute pnpm
run: pnpm install
- name: publish builder to ghcr.io
id: push-to-ghcr
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.builder-image.outputs.image }}
tags: ${{ steps.builder-image.outputs.tags }}
registry: ghcr.io/${{ github.repository_owner }}

- name: Run Build
run: pnpm build
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository_owner }}/extension-bootc-builder
subject-digest: ${{ steps.push-to-ghcr.outputs.digest }}
push-to-registry: true
Comment on lines +83 to +88
Copy link
Contributor

Choose a reason for hiding this comment

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

Very interesting 👁️


- name: Login to ghcr.io
run: podman login --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }} ghcr.io
extension-image:
name: Build and publish extension OCI image
if: always()
runs-on: ubuntu-24.04
needs: builder-image

- name: Publish Image
id: publish-image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/podman-desktop-extension-bootc
IMAGE_NIGHTLY=${IMAGE_NAME}:nightly
IMAGE_SHA=${IMAGE_NAME}:${GITHUB_SHA}
podman build -t $IMAGE_NIGHTLY .
podman push $IMAGE_NIGHTLY
podman tag $IMAGE_NIGHTLY $IMAGE_SHA
podman push $IMAGE_SHA
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: build extension image
id: extension-image
uses: redhat-actions/buildah-build@v2
with:
image: extension-bootc
tags: next ${{ github.sha }}
Copy link
Contributor

Choose a reason for hiding this comment

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

a tag with space?

Copy link
Contributor

Choose a reason for hiding this comment

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

this is a list of tags:

  • next
  • the sha of commit

archs: amd64, arm64
containerfiles: |
build/Containerfile
context: .
oci: true

- name: Log in to ghcr.io
uses: redhat-actions/podman-login@v1
with:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io

- name: publish extension to ghcr.io
id: push-to-ghcr
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.extension-image.outputs.image }}
tags: ${{ steps.extension-image.outputs.tags }}
registry: ghcr.io/${{ github.repository_owner }}

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository_owner }}/extension-bootc
subject-digest: ${{ steps.push-to-ghcr.outputs.digest }}
push-to-registry: true
118 changes: 95 additions & 23 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (C) 2023-2024 Red Hat, Inc.
# Copyright (C) 2023-2025 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -103,32 +103,104 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: pnpm/action-setup@v4
name: Install pnpm
builder-image:
needs: tag
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
run_install: false
ref: ${{ needs.tag.outputs.githubTag }}

- uses: actions/setup-node@v6
- name: build builder image
id: builder-image
uses: redhat-actions/buildah-build@v2
with:
node-version: 22
cache: 'pnpm'
image: extension-bootc-builder
tags: latest ${{ needs.tag.outputs.bootcExtensionVersion }}
platforms: linux/amd64, linux/arm64
containerfiles: |
build/Containerfile.builder
context: .
oci: true

- name: Execute pnpm
run: pnpm install
- name: Log in to ghcr.io
uses: redhat-actions/podman-login@v1
with:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io

- name: Run Build
run: pnpm build
- name: publish builder to ghcr.io
id: push-to-ghcr
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.builder-image.outputs.image }}
tags: ${{ steps.builder-image.outputs.tags }}
registry: ghcr.io/${{ github.repository_owner }}

- name: Login to ghcr.io
run: podman login --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }} ghcr.io
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository_owner }}/extension-bootc-builder
subject-digest: ${{ steps.push-to-ghcr.outputs.digest }}
push-to-registry: true

- name: Publish Image
id: publish-image
run: |
IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/podman-desktop-extension-bootc
IMAGE_WITH_TAG=${IMAGE_NAME}:${{ steps.TAG_UTIL.outputs.bootcExtensionVersion }}
IMAGE_LATEST=${IMAGE_NAME}:latest
podman build -t $IMAGE_WITH_TAG .
podman push $IMAGE_WITH_TAG
podman tag $IMAGE_WITH_TAG $IMAGE_LATEST
podman push $IMAGE_LATEST
extension-image:
name: Build and publish extension OCI image

runs-on: ubuntu-24.04
needs: [builder-image, tag]

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
Comment on lines +155 to +157
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

Inconsistent fetch-depth between jobs.

The extension-image job uses fetch-depth: 0 while builder-image doesn't specify it (defaults to 1). If both need full history for build context, they should be consistent.

Apply this diff if full history is needed for builder-image:

       - uses: actions/checkout@v6
         with:
           ref: ${{ needs.tag.outputs.githubTag }}
+          fetch-depth: 0

Otherwise, remove fetch-depth: 0 from extension-image if shallow clone is sufficient.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/release.yaml around lines 155-157: inconsistent fetch-depth
between jobs — extension-image sets fetch-depth: 0 while builder-image uses the
default shallow clone; decide which behavior is required and make them
consistent: if both jobs need full history, add with: fetch-depth: 0 to the
builder-image checkout step; otherwise remove the with: fetch-depth: 0 block
from the extension-image checkout so both use the default shallow clone.


- name: build extension image
id: extension-image
uses: redhat-actions/buildah-build@v2
with:
image: extension-bootc
tags: latest ${{ needs.tag.outputs.bootcExtensionVersion }}
archs: amd64, arm64
containerfiles: |
build/Containerfile
context: .
oci: true

- name: Log in to ghcr.io
uses: redhat-actions/podman-login@v1
with:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io

- name: publish extension to ghcr.io
id: push-to-ghcr
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.extension-image.outputs.image }}
tags: ${{ steps.extension-image.outputs.tags }}
registry: ghcr.io/${{ github.repository_owner }}

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository_owner }}/extension-bootc
subject-digest: ${{ steps.push-to-ghcr.outputs.digest }}
push-to-registry: true

release:
needs: [tag, builder-image, extension-image]
name: Release
runs-on: ubuntu-24.04
steps:
- name: id
run: echo the release id is ${{ needs.tag.outputs.releaseId}}

- name: Publish release
uses: StuYarrow/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
id: ${{ needs.tag.outputs.releaseId}}
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ In the below example, we will pretend that we're upgrading from `1.1.0` to `1.2.
1. Make sure that all tasks for the respective release milestone are completed / updated, then close it. https://github.com/podman-desktop/podman-desktop-extension-bootc/milestones
1. If not already created, click on `New Milestone` and create a new milestone for the NEXT release.
1. Check that https://github.com/podman-desktop/podman-desktop-extension-bootc/actions/workflows/release.yaml has been completed.
1. Ensure the image has been successfully published to https://github.com/podman-desktop/extension-bootc/pkgs/container/podman-desktop-extension-bootc
1. Ensure the image has been successfully published to https://github.com/podman-desktop/extension-bootc/pkgs/container/extension-bootc
1. There should be an automated PR that has been created. The title looks like `chore: 📢 Bump version to 1.3.0`. Rerun workflow manually if some of e2e tests are failing.
1. Wait for the PR above to be approved and merged before continuing with the steps.
1. Edit the new release https://github.com/podman-desktop/podman-desktop-extension-bootc/releases/edit/v1.2.0.
Expand Down
67 changes: 67 additions & 0 deletions build/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# Copyright (C) 2025 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

FROM ghcr.io/podman-desktop/extension-bootc-builder:next AS builder

WORKDIR /opt/app-root/extension-source

# copy source code
COPY --chown=1001:root *.js /opt/app-root/extension-source/
COPY --chown=1001:root .gitignore /opt/app-root/extension-source/
COPY --chown=1001:root *.json /opt/app-root/extension-source/
COPY --chown=1001:root packages /opt/app-root/extension-source/packages
COPY --chown=1001:root .npmrc /opt/app-root/extension-source/
COPY --chown=1001:root .gitignore /opt/app-root/extension-source/
COPY --chown=1001:root types /opt/app-root/extension-source/types

# refresh dependencies (if needed)
# and build the extension
RUN pnpm install && \
pnpm build

# copy output of the build + required files
RUN mkdir /opt/app-root/extension && \
cp -r packages/backend/dist /opt/app-root/extension/ && \
cp packages/backend/package.json /opt/app-root/extension/ && \
cp packages/backend/bootable.woff2 /opt/app-root/extension/ && \
cp packages/backend/icon.png /opt/app-root/extension/ && \
cp -r packages/backend/media/ /opt/app-root/extension/media

COPY LICENSE /opt/app-root/extension/
COPY README.md /opt/app-root/extension/

# TEMPORARY. Permanent fix will be in the future when we can add all of this to vite script.
# We require the macadam.js binaries and library, so we will manually copy this over to the container image.
# we rely on `pnpm build` before creating the container image, so we can safely assume that the macadam.js binaries are already present in the node_modules directory
# and can copy them over to the container image.
COPY node_modules/@crc-org/macadam.js /opt/app-root/extension/node_modules/@crc-org/macadam.js
Copy link
Contributor

Choose a reason for hiding this comment

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

question: I am a little bit concerned why those dependencies are handled manually and not through pnpm like the RHEL extension ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Me too, but this is unchanged from the current containerfile. As someone we know would say 'one PR one change'. :)

I see what the RHEL extension did and can replicate in a following PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure to understand as Containerfile(s) are new in this PR ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure to understand as Containerfile(s) are new in this PR ?

There is an existing Containerfile at the root of the repository

Copy link
Contributor

Choose a reason for hiding this comment

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

non-blocker, but a concern.

But any changes to the Containerfile that's outside-the-norm we should also check to see if it's correctly adding the arm64 madacam binary when building an arm64 container image.

Same for amd64, etc.

There were issues in the past where you would build this container image with podman build --platform linux/amd64 and because you are building it on macOS.. it adds the arm64 binary, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1 to this. I think I have a changeset that replicates what the RHEL extension build does, but that path changes where node_modules ends up in the final image and the structure of it (plus adds a few Mb). It would change this PR from 'it generates the identical extension as today, but we should still test' to 'the build output is fundamentally different so the only way to validate is testing on every platform'. I've opened #2136 to track this as a separate issue.

# Copy over ssh2 and it's dependencies (run jq '.dependencies' node_modules/ssh2/package.json locally to see)
COPY node_modules/ssh2 /opt/app-root/extension/node_modules/ssh2
COPY node_modules/asn1 /opt/app-root/extension/node_modules/asn1
COPY node_modules/bcrypt-pbkdf /opt/app-root/extension/node_modules/bcrypt-pbkdf
COPY node_modules/safer-buffer /opt/app-root/extension/node_modules/safer-buffer
COPY node_modules/tweetnacl /opt/app-root/extension/node_modules/tweetnacl

# Copy the extension to a new image
FROM scratch

LABEL org.opencontainers.image.title="Bootable Container Extension" \
org.opencontainers.image.description="Podman Desktop extension for bootable OS containers (bootc) and generating disk images" \
org.opencontainers.image.vendor="Red Hat" \
io.podman-desktop.api.version=">= 1.18.0"

COPY --from=builder /opt/app-root/extension /extension
31 changes: 31 additions & 0 deletions build/Containerfile.builder
Copy link
Contributor

Choose a reason for hiding this comment

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

I tried to build this Containerfile.builder but I am seing the following error:

Image Image

I tried to do a git clean -xdf

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
Copy link
Contributor

Choose a reason for hiding this comment

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

out of the context of this PR, but we need to revise the usage of a builder container, multi stage container exists, and we could use caching mechanism to make them as fast, and avoid publishing a builder image

# Copyright (C) 2025 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

FROM registry.access.redhat.com/ubi10/nodejs-22-minimal:10.1-1764649415
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check image version information from registry metadata if available
# and search for Node.js 22 security status

# First, let's check if there's any documentation in the repo about why this version was chosen
echo "=== Checking for any documentation about image versioning ==="
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "Dockerfile*" -o -name "Containerfile*" \) -exec grep -l "nodejs-22\|ubi10.*nodejs\|10\.1-1764649415" {} \;

# Search for any version management files
echo -e "\n=== Checking for version pinning rationale ==="
find . -type f \( -name ".dockerignore" -o -name "*.env" -o -name "versions.txt" -o -name "VERSIONS" \) 

# Check the Containerfile for any comments about version pinning
echo -e "\n=== Full Containerfile.builder content ==="
cat build/Containerfile.builder

# Check git history for when this version was pinned
echo -e "\n=== Recent git history for this file ==="
git log --oneline -10 build/Containerfile.builder 2>/dev/null || echo "Git history not available"

Repository: podman-desktop/extension-bootc

Length of output: 204


🌐 Web query:

UBI10 nodejs-22-minimal image version 10.1 security status Red Hat

💡 Result:

Short answer: The UBI10 nodejs-22-minimal images for RHEL 10.1 have received multiple security updates in 2025 (Important/High) addressing several Node.js CVEs — you should pull the latest 10.1 nodejs-22-minimal image and apply those updates. See Red Hat’s RHEL 10.1 notes and the RHSA advisories for nodejs:22 (examples: RHSA-2025:8493, RHSA-2025:1613; additional RHSA advisories exist). [1][2][3]

References

  • RHEL 10.1 release notes (nodejs/nodejs24 context). [1]
  • RHSA-2025:8493 (nodejs:22 — RHEL 10 advisory / CVE list). [2]
  • RHSA-2025:1613 (nodejs:22 — security fixes such as CVE-2025-22150, CVE-2025-23085, CVE-2025-23083). [3]

If you want, I can: (a) check the exact image digest/tag in the Red Hat Catalog for ubi10/nodejs-22-minimal:10.1, or (b) list the specific CVEs and RHSA IDs affecting the 10.1 build. Which would you prefer?


🌐 Web query:

Node.js 22 LTS security vulnerabilities 2025

💡 Result:

Short summary — Node.js 22.x (LTS) security issues in 2025

  • CVE-2025-23166 — improper error handling in async crypto (SignTraits::DeriveBits) allowing remote process crash (High). Patch released in May 2025. [1][2]
  • CVE-2025-23083 — diagnostics_channel exposes internal worker constructors affecting the Permission Model (--permission) (High). Published Jan 2025; patched in subsequent security releases. [3][4]
  • CVE-2025-23084 (and follow-up CVE-2025-27210) — Windows drive/device-name handling in path APIs (path.join / path.normalize) allowing path-traversal bypass on Windows (Medium/High follow-up). Fixes released in Jan–July 2025. [5][6]
  • CVE-2025-23165 — ReadFileUtf8 memory leak in fs internal binding that can lead to memory exhaustion (Low/DoS); patched in May 2025. [1][7]
  • Additional related fixes in 2025 (e.g., V8 HashDoS / llhttp upgrades) affected active release lines including 22.x; see Node.js security posts for details. [1][6]

Actionable steps

  • Upgrade to the latest patched 22.x release from the Node.js project (see the May/July 2025 security release notes). [1][6]
  • Audit native/third-party native modules and update dependencies (especially crypto and diagnostics-related packages).
  • If you run on Windows, pay special attention to path-related fixes and test path-handling code. [5][6]

Sources
[1] Node.js — May 14, 2025 Security Releases. (nodejs.org)
[2] Wiz vulnerability entry — CVE-2025-23166. (wiz.io)
[3] Ubuntu CVE page — CVE-2025-23083. (ubuntu.com)
[4] Wiz vulnerability entry — CVE-2025-23083. (wiz.io)
[5] Ubuntu CVE page — CVE-2025-23084 (path/join Windows). (ubuntu.com)
[6] Node.js — July 15, 2025 Security Releases (follow-up fixes). (nodejs.org)
[7] Coverage mentioning ReadFileUtf8 memory leak and release versions. (timber-dev.net)

If you want, I can: (A) check the exact latest 22.x patch version for your platform and provide upgrade commands, or (B) fetch the full Node.js release notes and CVE text. Which would you prefer?

Citations:


Update the base image to the latest 10.1- build.*

The pinned version 10.1-1764649415 is outdated. Red Hat released multiple security updates for ubi10/nodejs-22-minimal throughout 2025 (RHSA-2025:8493, RHSA-2025:1613) addressing high-severity Node.js 22 CVEs including CVE-2025-23166 (async crypto), CVE-2025-23083 (Permission Model bypass), and CVE-2025-23084 (path traversal). Pull the latest ubi10/nodejs-22-minimal:10.1-* image to include these patches.

🧰 Tools
🪛 RuboCop (1.81.7)

[fatal] 18-18: unexpected token tCOLON
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🤖 Prompt for AI Agents
In build/Containerfile.builder around line 18, the base image is pinned to an
outdated tag (10.1-1764649415); update that FROM line to reference the latest
10.1-* build (e.g., replace the fixed tag with the 10.1-* tag or a CI-driven
variable that resolves to the latest 10.1 image), rebuild the image so Docker
pulls the newest ubi10/nodejs-22-minimal:10.1-* manifest, and then run your
container image build and security scan to verify the updated image includes the
RHSA-2025 fixes before merging.


# change home directory to be at /opt/app-root
ENV HOME=/opt/app-root

# copy the application files to the /opt/app-root/extension-source directory
WORKDIR /opt/app-root/extension-source
RUN mkdir -p /opt/app-root/extension-source
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc /opt/app-root/extension-source/
COPY packages/backend/package.json /opt/app-root/extension-source/packages/backend/package.json
COPY packages/frontend/package.json /opt/app-root/extension-source/packages/frontend/package.json

RUN npm install --global pnpm@10 && \
pnpm --frozen-lockfile install