You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/05/18 13:49:47 UTC
(skywalking-horizon-ui) branch main updated (1fd9659 -> 80565f5)
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
from 1fd9659 logging: treat local `node dist/server.js` as production (quiet); only `pnpm dev` is dev
new c4df7d8 docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image
new 80565f5 ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.github/workflows/publish-image.yaml | 200 ++++++++++++++++++++++++-----------
Dockerfile | 2 +
apps/bff/src/config/schema.ts | 16 ++-
3 files changed, 154 insertions(+), 64 deletions(-)
(skywalking-horizon-ui) 02/02: ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list
Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 80565f563b6ea47c274141029d8ef6bd33b7eeac
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Mon May 18 21:49:27 2026 +0800
ci: multi-arch publish-image — native amd64 + arm64 builds, OCI manifest list
The previous single-arch (linux/amd64) image worked on amd64 hosts but
forced Apple Silicon Macs and AWS Graviton boxes through Rosetta /
QEMU translation — argon2's CPU-intensive hashing took the brunt of
the slowdown. With this change the canonical SHA tag on GHCR resolves
to the host's native arch automatically.
structure
- New `tags` job computes the canonical SHA + moving tag set (`:main`,
`:vX.Y.Z`, `:latest`, `:major.minor`) once and outputs them so the
matrix build + the manifest job share the same vocabulary without
duplicating the shell math.
- `build` matrix runs on native runners — `ubuntu-latest` for amd64,
`ubuntu-24.04-arm` for arm64 (free for public ASF repos). Each
produces its own `./dist/` with the correct argon2 native binding,
pushes `<base>:<sha>-<arch>`, and uses an arch-scoped GHA cache.
- `manifest` job stitches the two `:<sha>-<arch>` tags into a single
OCI manifest list per moving tag via `docker buildx imagetools
create`. Pulling the canonical `:<sha>` (or `:main`, `:vX.Y.Z`, etc.)
from any host selects the matching arch.
trade-offs / follow-ups
- If the org doesn't have `ubuntu-24.04-arm` runners enabled, the
arm64 build fails and the manifest step short-circuits. Fix is org-
side (enable the runner) or drop the arm64 matrix entry and accept
Rosetta-emulation on Macs.
- Per-arch debugger tags (`<sha>-amd64`, `<sha>-arm64`) remain
permanently pushed so investigators can pull a specific arch without
going through the manifest.
---
.github/workflows/publish-image.yaml | 200 ++++++++++++++++++++++++-----------
1 file changed, 138 insertions(+), 62 deletions(-)
diff --git a/.github/workflows/publish-image.yaml b/.github/workflows/publish-image.yaml
index 0fe0a9a..4387d5c 100644
--- a/.github/workflows/publish-image.yaml
+++ b/.github/workflows/publish-image.yaml
@@ -14,19 +14,38 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-# Publish a Horizon UI container image to GHCR on:
+# Publish a multi-arch Horizon UI container image to GHCR on:
# - push to `main` (tagged with `main` + the full commit SHA)
# - any `v*` tag (tagged with the version + the full commit SHA)
#
-# The full commit SHA is the canonical, immutable identifier — moving
-# tags like `main` and `vX.Y.Z` are conveniences that point at the same
-# SHA-built image. Operators should pull by SHA in production.
+# Architecture story
+# The image is a pure copy-in of `./dist/` (no compile or network
+# inside `docker build`). The `dist/node_modules/` carries `argon2`'s
+# native binding — that binding is platform-specific, so a single
+# amd64-built dist/ cannot be packaged into an arm64-correct image.
+# We build each architecture on its own native GitHub runner
+# (`ubuntu-latest` for amd64, `ubuntu-24.04-arm` for arm64), push a
+# per-arch SHA-suffix tag, and a final `manifest` job stitches them
+# into a single SHA-canonical OCI manifest list. Pulling the canonical
+# tag from any host (Apple Silicon, AWS Graviton, plain x86_64)
+# selects the right arch automatically — no Rosetta / QEMU emulation
+# needed at run time.
#
-# Only login / setup-qemu / setup-buildx are pulled from `docker/*` —
-# everything else (tag computation, buildx build+push) is shell-driven
-# to stay within ASF infra's third-party-action allow-list. SHAs are
-# the same ones `apache/skywalking`'s publish-docker.yaml uses, so
-# they're already vetted at the org level.
+# Tagging
+# - `:<40-char-sha>` always (canonical, immutable)
+# - `:main` on push to main
+# - `:<ver>` + `:latest` on v* tags (ver = the tag with leading `v` stripped)
+# - `:<major.minor>` on v* tags (e.g. `1.2` from `v1.2.3`)
+#
+# Per-arch interim tags
+# - `:<sha>-amd64` / `:<sha>-arm64` are also pushed so a debugger can
+# pull a specific arch directly. The manifest job is the only thing
+# that exposes the canonical tag without an arch suffix.
+#
+# Only login / setup-buildx / setup-node are pulled from third-party
+# actions — everything else (tag computation, buildx build+push,
+# manifest stitching) is shell-driven to stay within ASF infra's
+# third-party-action allow-list. SHAs are vetted at the org level.
name: publish-image
on:
@@ -45,22 +64,77 @@ concurrency:
cancel-in-progress: false
jobs:
- build-and-push:
+ # ── Compute tags once so the per-arch matrix + the manifest job all
+ # ── work from the same set without duplicating the shell math.
+ tags:
if: github.repository == 'apache/skywalking-horizon-ui'
- name: Build + push
+ name: Compute image tags
runs-on: ubuntu-latest
+ outputs:
+ base: ${{ steps.tags.outputs.base }}
+ moving: ${{ steps.tags.outputs.moving }}
+ steps:
+ - id: tags
+ env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+ run: |
+ set -eu
+ base="${REGISTRY}/${IMAGE_NAME}"
+ # lower-case the image path — GHCR rejects mixed-case names.
+ base="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
+ sha="${{ github.sha }}"
+ # `moving` = the SET of canonical/moving tags the manifest
+ # job needs to stitch (one per `imagetools create -t ...`).
+ # Space-separated; consumers split on whitespace.
+ moving="${base}:${sha}"
+ if [ "${{ github.ref }}" = "refs/heads/main" ]; then
+ moving="${moving} ${base}:main"
+ fi
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ ver="${GITHUB_REF_NAME#v}"
+ moving="${moving} ${base}:${ver} ${base}:latest"
+ mm="${ver%.*}"
+ if [ "${mm}" != "${ver}" ]; then
+ moving="${moving} ${base}:${mm}"
+ fi
+ fi
+ echo "base=${base}" >> "$GITHUB_OUTPUT"
+ echo "moving=${moving}" >> "$GITHUB_OUTPUT"
+ echo "::notice::Canonical SHA: ${sha}"
+ echo "::notice::Moving tags: ${moving}"
+
+ # ── Per-arch native build. Each runner produces its own `./dist/`
+ # ── (with the correct argon2 native binding) and pushes
+ # ── `<base>:<sha>-<arch>`. The manifest job below stitches the
+ # ── `:<sha>-amd64` + `:<sha>-arm64` pair into the canonical tag set.
+ build:
+ if: github.repository == 'apache/skywalking-horizon-ui'
+ needs: tags
+ name: Build ${{ matrix.arch }}
+ runs-on: ${{ matrix.runner }}
timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - runner: ubuntu-latest
+ platform: linux/amd64
+ arch: amd64
+ # Free arm64 runners for public repos. If the org doesn't have
+ # them enabled, this matrix entry fails and the manifest job
+ # below short-circuits — fix is to either enable them on the
+ # org or drop arm64 here and accept Rosetta-emulation on Macs.
+ - runner: ubuntu-24.04-arm
+ platform: linux/arm64
+ arch: arm64
env:
- REGISTRY: ghcr.io
- IMAGE_NAME: ${{ github.repository }}
+ BASE: ${{ needs.tags.outputs.base }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- # The Dockerfile is a pure copy-in of `./dist/` (no compile or
- # network inside `docker build`). Build the artifact on the
- # runner first; the image just lays it out under /app.
- name: Set up Node
uses: actions/setup-node@v4
with:
@@ -72,71 +146,73 @@ jobs:
- name: Install workspace deps
run: pnpm install --frozen-lockfile
- - name: Build self-contained ./dist/
+ - name: Build self-contained ./dist/ (native ${{ matrix.arch }})
+ # `pnpm package` runs `pnpm deploy` whose `node_modules` contains
+ # the argon2 binding for the current runner's architecture.
+ # Running this on `ubuntu-24.04-arm` produces an arm64-correct
+ # tree; on `ubuntu-latest` it's amd64.
run: pnpm package
- - name: Set up QEMU
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392
-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- name: Log in to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
- registry: ${{ env.REGISTRY }}
+ registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- # Compute image tags in shell so we don't need docker/metadata-action.
- # Canonical = full 40-char SHA; moving tags layered per trigger.
- - name: Compute image tags
- id: tags
+ - name: Build + push (${{ matrix.arch }})
run: |
set -eu
- base="${REGISTRY}/${IMAGE_NAME}"
- # lower-case the image path — GHCR rejects mixed-case names.
- base="$(echo "$base" | tr '[:upper:]' '[:lower:]')"
- sha="${{ github.sha }}"
- tags="-t ${base}:${sha}"
- if [ "${{ github.ref }}" = "refs/heads/main" ]; then
- tags="${tags} -t ${base}:main"
- fi
- if [ "${{ github.ref_type }}" = "tag" ]; then
- # Strip the leading `v` from `vX.Y.Z` for the semver tag.
- ver="${GITHUB_REF_NAME#v}"
- tags="${tags} -t ${base}:${ver} -t ${base}:latest"
- # Bonus: major.minor (e.g. 1.2 from 1.2.3) for the common
- # "track the minor line" usage. POSIX shell substitution.
- mm="${ver%.*}"
- if [ "${mm}" != "${ver}" ]; then
- tags="${tags} -t ${base}:${mm}"
- fi
- fi
- echo "IMAGE_BASE=${base}" >> "$GITHUB_ENV"
- echo "TAG_ARGS=${tags}" >> "$GITHUB_ENV"
- echo "tags=${tags}" >> "$GITHUB_OUTPUT"
-
- - name: Build + push
- run: |
- set -eu
- # Single-platform (linux/amd64) for now. The `./dist/` is built
- # on the GitHub-hosted runner (linux/amd64), and `argon2` ships
- # native bindings — cross-arch packaging would need a per-arch
- # matrix of build jobs that each produce their own dist/ +
- # platform-tagged image, then a buildx imagetools merge into a
- # manifest list. Easy follow-up when arm64 demand is real.
docker buildx build \
- --platform linux/amd64 \
+ --platform ${{ matrix.platform }} \
--file Dockerfile \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.title=Apache SkyWalking Horizon UI" \
--label "org.opencontainers.image.description=Next-generation web UI for Apache SkyWalking." \
--label "org.opencontainers.image.licenses=Apache-2.0" \
- --cache-from type=gha \
- --cache-to type=gha,mode=max \
- ${TAG_ARGS} \
+ --cache-from type=gha,scope=${{ matrix.arch }} \
+ --cache-to type=gha,mode=max,scope=${{ matrix.arch }} \
+ -t "${BASE}:${{ github.sha }}-${{ matrix.arch }}" \
--push \
.
- echo "::notice::Published ${IMAGE_BASE}@sha=${{ github.sha }}"
+ echo "::notice::Pushed ${BASE}:${{ github.sha }}-${{ matrix.arch }}"
+
+ # ── Stitch the per-arch tags into a single OCI manifest list per
+ # ── canonical/moving tag. Pulling any of these tags from any host
+ # ── selects the matching arch automatically.
+ manifest:
+ if: github.repository == 'apache/skywalking-horizon-ui'
+ needs: [tags, build]
+ name: Stitch multi-arch manifest
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ env:
+ BASE: ${{ needs.tags.outputs.base }}
+ MOVING: ${{ needs.tags.outputs.moving }}
+ steps:
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
+
+ - name: Log in to GHCR
+ uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Create manifest list per moving tag
+ run: |
+ set -eu
+ amd="${BASE}:${{ github.sha }}-amd64"
+ arm="${BASE}:${{ github.sha }}-arm64"
+ for tag in ${MOVING}; do
+ echo "::group::Stitch ${tag}"
+ docker buildx imagetools create -t "${tag}" "${amd}" "${arm}"
+ docker buildx imagetools inspect "${tag}" | head -40
+ echo "::endgroup::"
+ done
+ echo "::notice::Published multi-arch manifest @sha=${{ github.sha }} (amd64+arm64)"
(skywalking-horizon-ui) 01/02: docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image
Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit c4df7d8cc0ae8644ecf84acc79654489886ac651
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Mon May 18 21:47:34 2026 +0800
docker: zero-config boot — HORIZON_SERVER_HOST=0.0.0.0 by default in the image
Without this the container booted but bound `127.0.0.1` (the schema
default), which inside Docker is container-internal-loopback —
`docker run -p 8081:8081 horizon-ui:local` (no config mount) reached
the published port but got 502s with no clear cause. An operator
trying the image with zero arguments hit this immediately.
Added env-var fallbacks for `server.host` / `server.port` in the
config schema (same pattern as the existing `HORIZON_AUDIT_FILE` /
`HORIZON_SETUP_FILE` / `HORIZON_ALARMS_FILE` / `HORIZON_WIRE_LOG_FILE`
overrides). An explicit value in `horizon.yaml` still wins.
The Dockerfile sets `HORIZON_SERVER_HOST=0.0.0.0` and
`HORIZON_SERVER_PORT=8081` so the image's out-of-the-box behaviour is:
- boot in unconfigured state (warns, doesn't crash — already wired)
- bind to 0.0.0.0:8081 so the published port reaches the BFF
- serve the login page showing the "Auth not configured" banner
Verified: `docker run --rm -p 8081:8081 horizon-ui:local` reaches the
SPA + API; health endpoint reports configured: false + setupHint; logs
quiet at default error level; LOG_LEVEL=info opens the access log.
---
Dockerfile | 2 ++
apps/bff/src/config/schema.ts | 16 ++++++++++++++--
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index c55f6d0..336b28a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -59,6 +59,8 @@ RUN mkdir -p /data && chown horizon:horizon /data
VOLUME ["/data"]
ENV NODE_ENV=production \
+ HORIZON_SERVER_HOST=0.0.0.0 \
+ HORIZON_SERVER_PORT=8081 \
HORIZON_STATIC_DIR=/app/static \
HORIZON_CONFIG=/app/horizon.yaml \
HORIZON_AUDIT_FILE=/data/horizon-audit.jsonl \
diff --git a/apps/bff/src/config/schema.ts b/apps/bff/src/config/schema.ts
index ecb3aed..bd4b118 100644
--- a/apps/bff/src/config/schema.ts
+++ b/apps/bff/src/config/schema.ts
@@ -17,10 +17,22 @@
import { z } from 'zod';
+// Env-var-overridable bind defaults. The Docker image sets
+// `HORIZON_SERVER_HOST=0.0.0.0` so a zero-config `docker run -p 8081:8081
+// horizon-ui:local` reaches the BFF (the YAML default `127.0.0.1` would
+// bind container-loopback and silently 502 from the host side). An
+// explicit `server.host`/`server.port` in horizon.yaml always wins.
+const serverHostDefault = process.env.HORIZON_SERVER_HOST ?? '127.0.0.1';
+const serverPortDefault = (() => {
+ const raw = process.env.HORIZON_SERVER_PORT;
+ if (!raw) return 8081;
+ const n = Number(raw);
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 8081;
+})();
const serverSchema = z
.object({
- host: z.string().default('127.0.0.1'),
- port: z.number().int().positive().default(8081),
+ host: z.string().default(serverHostDefault),
+ port: z.number().int().positive().default(serverPortDefault),
staticDir: z.string().optional(),
})
.strict();