Build docker image containing modules (#34657)
Switch to bake to simplify the building of 2 images
This commit is contained in:
@@ -18,7 +18,8 @@ jobs:
|
|||||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||||
packages: write # needed for publishing packages to GHCR
|
packages: write # needed for publishing packages to GHCR
|
||||||
env:
|
env:
|
||||||
TEST_TAG: vectorim/element-web:test
|
TEST_TAG: ghcr.io/element-hq/element-web:test
|
||||||
|
TEST_TAG_MODULES: ghcr.io/element-hq/element-web/modules:test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
@@ -38,16 +39,18 @@ jobs:
|
|||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Build and load
|
- name: Build and load
|
||||||
id: test-build
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
|
||||||
with:
|
with:
|
||||||
context: .
|
source: .
|
||||||
file: apps/web/Dockerfile
|
files: ./docker-bake.hcl
|
||||||
load: true
|
load: true
|
||||||
|
set: |
|
||||||
|
element-web.tags=${{ env.TEST_TAG }}
|
||||||
|
element-web-modules.tags=${{ env.TEST_TAG_MODULES }}
|
||||||
|
|
||||||
- name: Test the image
|
- name: Test the image
|
||||||
env:
|
env:
|
||||||
IMAGEID: ${{ steps.test-build.outputs.imageid }}
|
IMAGE: ${{ env.TEST_TAG }}
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
@@ -65,7 +68,7 @@ jobs:
|
|||||||
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
||||||
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
||||||
-v $(pwd)/test_modules:/modules \
|
-v $(pwd)/test_modules:/modules \
|
||||||
"$IMAGEID" \
|
"$IMAGE" \
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run some smoke tests
|
# Run some smoke tests
|
||||||
@@ -81,6 +84,41 @@ jobs:
|
|||||||
# Clean up
|
# Clean up
|
||||||
docker stop "$CONTAINER_ID"
|
docker stop "$CONTAINER_ID"
|
||||||
|
|
||||||
|
- name: Test the modules image
|
||||||
|
env:
|
||||||
|
IMAGE: ${{ env.TEST_TAG_MODULES }}
|
||||||
|
timeout-minutes: 2
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
|
||||||
|
ELEMENT_WEB_PORT=8182
|
||||||
|
CONTAINER_ID=$(
|
||||||
|
docker run \
|
||||||
|
--rm \
|
||||||
|
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
||||||
|
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
||||||
|
"$IMAGE" \
|
||||||
|
)
|
||||||
|
|
||||||
|
# Every module pinned in the Dockerfile should be listed in the config and served
|
||||||
|
MODULES=$(grep -oE '^ARG MODULE_[A-Z0-9_]+_VERSION=' apps/web/Dockerfile |
|
||||||
|
sed -E 's/^ARG MODULE_(.*)_VERSION=$/\1/' | tr '[:upper:]_' '[:lower:]-')
|
||||||
|
test -n "$MODULES"
|
||||||
|
|
||||||
|
CONFIG=$(wget --retry-connrefused --tries=5 -q --wait=3 -O - "http://localhost:$ELEMENT_WEB_PORT/config.json")
|
||||||
|
for MODULE in $MODULES; do
|
||||||
|
echo "$CONFIG" | jq -e --arg m "/modules/$MODULE/index.js" '.modules | index($m)'
|
||||||
|
wget -q --spider "http://localhost:$ELEMENT_WEB_PORT/modules/$MODULE/index.js"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check healthcheck
|
||||||
|
until test "$(docker inspect -f '{{.State.Health.Status}}' "$CONTAINER_ID")" == "healthy"; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker stop "$CONTAINER_ID"
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||||
@@ -95,6 +133,20 @@ jobs:
|
|||||||
type=ref,event=tag
|
type=ref,event=tag
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
||||||
|
bake-target: docker-metadata-action
|
||||||
|
|
||||||
|
- name: Docker meta (modules)
|
||||||
|
id: meta-modules
|
||||||
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
with:
|
||||||
|
images: ghcr.io/element-hq/element-web/modules
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=tag
|
||||||
|
flavor: |
|
||||||
|
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
|
||||||
|
bake-target: docker-metadata-action-modules
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
|
||||||
@@ -147,28 +199,37 @@ jobs:
|
|||||||
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
|
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
|
||||||
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
|
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
|
||||||
|
|
||||||
|
# Both targets are built in a single bake so that the app is only compiled once.
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
context: .
|
source: .
|
||||||
file: apps/web/Dockerfile
|
files: |
|
||||||
|
./docker-bake.hcl
|
||||||
|
${{ steps.meta.outputs.bake-file }}
|
||||||
|
${{ steps.meta-modules.outputs.bake-file }}
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
vars: |
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
PLATFORMS=linux/amd64,linux/arm64
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
|
|
||||||
- name: Sign the images with GitHub OIDC Token
|
- name: Sign the images with GitHub OIDC Token
|
||||||
env:
|
env:
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
METADATA: ${{ steps.build-and-push.outputs.metadata }}
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
TAGS: ${{ steps.meta.outputs.tags }}
|
||||||
|
TAGS_MODULES: ${{ steps.meta-modules.outputs.tags }}
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
images=""
|
images=""
|
||||||
for tag in ${TAGS}; do
|
sign_target() {
|
||||||
images+="${tag}@${DIGEST} "
|
digest=$(echo "$METADATA" | jq -er --arg t "$1" '.[$t]."containerimage.digest"')
|
||||||
|
for tag in $2; do
|
||||||
|
images+="${tag}@${digest} "
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
sign_target element-web "$TAGS"
|
||||||
|
sign_target element-web-modules "$TAGS_MODULES"
|
||||||
cosign sign --yes ${images}
|
cosign sign --yes ${images}
|
||||||
|
|
||||||
- name: Update repo description
|
- name: Update repo description
|
||||||
|
|||||||
+37
-1
@@ -30,7 +30,7 @@ RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-package.s
|
|||||||
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
|
RUN cp /src/apps/web/config.sample.json /src/apps/web/webapp/config.json
|
||||||
|
|
||||||
# App
|
# App
|
||||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:22f839c5fb4007dc24d203a170a9e03fc185d660bfefc34ac6823a7aef085cbc
|
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:22f839c5fb4007dc24d203a170a9e03fc185d660bfefc34ac6823a7aef085cbc AS element_web
|
||||||
|
|
||||||
# Need root user to install packages & manipulate the usr directory
|
# Need root user to install packages & manipulate the usr directory
|
||||||
USER root
|
USER root
|
||||||
@@ -55,3 +55,39 @@ USER nginx
|
|||||||
ENV ELEMENT_WEB_PORT=80
|
ENV ELEMENT_WEB_PORT=80
|
||||||
|
|
||||||
HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json
|
HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json
|
||||||
|
|
||||||
|
# Modules are consumed as prebuilt release artifacts rather than built from source.
|
||||||
|
# Each module is pinned to a version and the sha256 of its release archive.
|
||||||
|
FROM --platform=$BUILDPLATFORM alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce AS modules
|
||||||
|
|
||||||
|
ARG MODULE_BANNER_VERSION=v1.0.0
|
||||||
|
ADD --checksum=sha256:8aabd9d43d40ffb499050246f7968323a08895b6f53bd48a71d4c7f0daf96634 \
|
||||||
|
https://github.com/element-hq/element-web/releases/download/module%2Fbanner%2F${MODULE_BANNER_VERSION}/banner-${MODULE_BANNER_VERSION}.zip \
|
||||||
|
/tmp/modules/banner.zip
|
||||||
|
|
||||||
|
ARG MODULE_RESTRICTED_GUESTS_VERSION=v1.0.0
|
||||||
|
ADD --checksum=sha256:d883327469ae78504a4e0aa8ebf2bbf525a9407ab10430ef42cf9338cc0821bb \
|
||||||
|
https://github.com/element-hq/element-web/releases/download/module%2Frestricted-guests%2F${MODULE_RESTRICTED_GUESTS_VERSION}/restricted-guests-${MODULE_RESTRICTED_GUESTS_VERSION}.zip \
|
||||||
|
/tmp/modules/restricted-guests.zip
|
||||||
|
|
||||||
|
ARG MODULE_WIDGET_LIFECYCLE_VERSION=v1.0.0
|
||||||
|
ADD --checksum=sha256:125e5a7a045e3cebee2c82ca30a477ebc8e31ee3bd139ae46e177612c25cc988 \
|
||||||
|
https://github.com/element-hq/element-web/releases/download/module%2Fwidget-lifecycle%2F${MODULE_WIDGET_LIFECYCLE_VERSION}/widget-lifecycle-${MODULE_WIDGET_LIFECYCLE_VERSION}.zip \
|
||||||
|
/tmp/modules/widget-lifecycle.zip
|
||||||
|
|
||||||
|
ARG MODULE_WIDGET_TOGGLES_VERSION=v1.0.0
|
||||||
|
ADD --checksum=sha256:27b0d0d9d803c41855aa94f02493ce214321f8d0c65af500d85875a3ef20efb0 \
|
||||||
|
https://github.com/element-hq/element-web/releases/download/module%2Fwidget-toggles%2F${MODULE_WIDGET_TOGGLES_VERSION}/widget-toggles-${MODULE_WIDGET_TOGGLES_VERSION}.zip \
|
||||||
|
/tmp/modules/widget-toggles.zip
|
||||||
|
|
||||||
|
# Unpack the modules
|
||||||
|
RUN apk add --no-cache unzip && \
|
||||||
|
for archive in /tmp/modules/*.zip; do \
|
||||||
|
name=$(basename "$archive" .zip); \
|
||||||
|
mkdir -p "/modules/$name" && unzip -q "$archive" -d "/modules/$name"; \
|
||||||
|
done
|
||||||
|
|
||||||
|
# Target with element_web + `/modules` copied in
|
||||||
|
FROM element_web AS element_web_modules
|
||||||
|
|
||||||
|
COPY --from=modules /modules /modules
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Builds the Element Web images. Both targets share the `builder` and `element_web` stages,
|
||||||
|
# building them together in a single bake invocation compiles the app only once.
|
||||||
|
#
|
||||||
|
# docker buildx bake # both images, for the local platform
|
||||||
|
# docker buildx bake element-web # just the base image
|
||||||
|
# PLATFORMS=linux/amd64,linux/arm64 docker buildx bake --push
|
||||||
|
|
||||||
|
variable "PLATFORMS" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Populated by docker/metadata-action in CI; empty locally.
|
||||||
|
target "docker-metadata-action" {}
|
||||||
|
target "docker-metadata-action-modules" {}
|
||||||
|
|
||||||
|
target "_common" {
|
||||||
|
context = "."
|
||||||
|
dockerfile = "apps/web/Dockerfile"
|
||||||
|
platforms = PLATFORMS == "" ? null : split(",", PLATFORMS)
|
||||||
|
}
|
||||||
|
|
||||||
|
# The stock image, with no modules bundled.
|
||||||
|
target "element-web" {
|
||||||
|
inherits = ["_common", "docker-metadata-action"]
|
||||||
|
target = "element_web"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The same image with the released Element modules baked into /modules,
|
||||||
|
# where the entrypoint picks them up and adds them to config.json.
|
||||||
|
target "element-web-modules" {
|
||||||
|
inherits = ["_common", "docker-metadata-action-modules"]
|
||||||
|
target = "element_web_modules"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["element-web", "element-web-modules"]
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
ARG ELEMENT_VERSION=latest@sha256:9f9b546280e67e492d56f6e775c449e74356bffb98daaf211abc755276713fad
|
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM node:lts-alpine@sha256:f70403e87646dc51b45295f4b8b70cdad0b63d2297c4c9899119b03f7af7a6b3 AS builder
|
|
||||||
|
|
||||||
ARG BUILD_CONTEXT
|
|
||||||
|
|
||||||
RUN apk add --no-cache jq
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package.json yarn.lock ./
|
|
||||||
# Copy the package.json files of all modules & packages to ensure the frozen workspace lockfile holds up
|
|
||||||
RUN --mount=type=bind,target=/docker-context \
|
|
||||||
cd /docker-context/; \
|
|
||||||
find . -path ./node_modules -prune -o -name "package.json" -mindepth 0 -maxdepth 4 -exec cp --parents "{}" /app/ \;
|
|
||||||
RUN yarn install --frozen-lockfile --ignore-scripts
|
|
||||||
COPY tsconfig.json ./
|
|
||||||
COPY ./$BUILD_CONTEXT ./$BUILD_CONTEXT
|
|
||||||
RUN cd $BUILD_CONTEXT && yarn vite build
|
|
||||||
RUN mkdir /modules
|
|
||||||
RUN cp -r ./$BUILD_CONTEXT/lib/ /modules/$(jq -r '"\(.name)-v\(.version)"' ./$BUILD_CONTEXT/package.json)
|
|
||||||
|
|
||||||
FROM ghcr.io/element-hq/element-web:${ELEMENT_VERSION}
|
|
||||||
|
|
||||||
COPY --from=builder /modules /modules/
|
|
||||||
Reference in New Issue
Block a user