From d221cb7b913edcedd707f1240d1cb3d61c29091b Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Mon, 18 May 2026 17:55:14 -0300 Subject: [PATCH 1/4] ci(docker): add Alpine 3.21 multi-arch image variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a parallel Alpine-based image alongside the existing Ubuntu images. Reuses the static musl pktvisord/pktvisor-reader binaries already produced by build_cross.yml for x86_64 and aarch64, so the new image inherits the existing cross-build pipeline without any toolchain or recipe changes. New files: - docker/Dockerfile.alpine: alpine:3.21 runtime, no crashpad (musl static binaries are built without sentry-crashpad per conanfile.py's libc==musl guard). - docker/alpine/entry.sh + run.sh: POSIX sh equivalents of the bash-based entry-cp.sh / run.sh, dropping the crashpad flags from pktvisord's invocation. Workflow: - build_cross.yml gains package-alpine (matrix on x86_64/aarch64) and merge-alpine jobs. They consume the existing pkvisor matrix artifacts, build pktvisor-cli inline, push per-arch images by digest, and merge into a single develop-alpine manifest. - Gated on push to develop only — PR runs still produce only the binary artifacts. Release tag (-alpine) is intentionally deferred; that will touch build-release.yml as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_cross.yml | 111 ++++++++++++++++++++++++++++++ docker/Dockerfile.alpine | 23 +++++++ docker/alpine/entry.sh | 61 ++++++++++++++++ docker/alpine/run.sh | 10 +++ 4 files changed, 205 insertions(+) create mode 100644 docker/Dockerfile.alpine create mode 100644 docker/alpine/entry.sh create mode 100644 docker/alpine/run.sh diff --git a/.github/workflows/build_cross.yml b/.github/workflows/build_cross.yml index 2848293f5..525d76862 100644 --- a/.github/workflows/build_cross.yml +++ b/.github/workflows/build_cross.yml @@ -220,3 +220,114 @@ jobs: name: pktvisor-cli-${{matrix.os}}-${{matrix.arch}} path: pktvisor-cli retention-days: 7 + + package-alpine: + name: alpine-${{matrix.arch}} + needs: pkvisor + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + docker_platform: linux/amd64 + goarch: amd64 + - arch: aarch64 + docker_platform: linux/arm64 + goarch: arm64 + steps: + - uses: actions/checkout@v6 + + - name: Download pktvisord + uses: actions/download-artifact@v8 + with: + name: pktvisord-linux-${{matrix.arch}}-static + path: ./ + + - name: Download pktvisor-reader + uses: actions/download-artifact@v8 + with: + name: pktvisor-reader-linux-${{matrix.arch}}-static + path: ./ + + - name: Stage pktvisor sources for go build + run: | + mv src pktvisor-src + cp -rpf golang/pkg/client/version.go . + + - name: Build pktvisor-cli + uses: ./.github/actions/build-go + with: + context: "." + file: "./Dockerfile" + goos: "linux" + goarch: "${{matrix.goarch}}" + + - name: Stage IANA CSV + run: cp pktvisor-src/tests/fixtures/pktvisor-port-service-names.csv ./custom-iana.csv + + - name: Login to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0 + + - name: Build + push pktvisor-alpine + id: docker_build + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f #v7.1.0 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: ./docker/Dockerfile.alpine + platforms: ${{matrix.docker_platform}} + outputs: type=image,"name=netboxlabs/pktvisor",push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.docker_build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v7 + with: + name: digests-alpine-${{matrix.arch}} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge-alpine: + needs: package-alpine + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + steps: + - name: Download digests + uses: actions/download-artifact@v8 + with: + path: /tmp/digests + pattern: digests-alpine-* + merge-multiple: true + + - name: Login to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0 + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + -t netboxlabs/pktvisor:develop-alpine \ + $(printf 'netboxlabs/pktvisor@sha256:%s ' *) + + - name: Inspect image + run: docker buildx imagetools inspect netboxlabs/pktvisor:develop-alpine diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine new file mode 100644 index 000000000..ccd029bf3 --- /dev/null +++ b/docker/Dockerfile.alpine @@ -0,0 +1,23 @@ +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates curl wget gzip + +COPY ./pktvisord /usr/local/sbin/pktvisord +COPY ./pktvisor-reader /usr/local/sbin/pktvisor-reader +COPY ./pktvisor-cli /usr/local/bin/pktvisor-cli +COPY ./docker/alpine/entry.sh /entry.sh +COPY ./docker/alpine/run.sh /run.sh + +RUN mkdir /geo-db \ + && wget -q -O /geo-db/asn.mmdb.gz https://github.com/orb-community/geo-asn-database/raw/main/asn.mmdb.gz \ + && wget -q -O /geo-db/city.mmdb.gz https://github.com/orb-community/geo-asn-database/raw/main/city.mmdb.gz + +RUN mkdir /iana +COPY ./custom-iana.csv /iana/custom-iana.csv + +RUN chmod a+x /usr/local/sbin/pktvisord \ + /usr/local/sbin/pktvisor-reader \ + /usr/local/bin/pktvisor-cli \ + /entry.sh /run.sh + +ENTRYPOINT [ "/entry.sh" ] diff --git a/docker/alpine/entry.sh b/docker/alpine/entry.sh new file mode 100644 index 000000000..ccadde521 --- /dev/null +++ b/docker/alpine/entry.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# Entry point for the Alpine-based pktvisor image. POSIX sh (no bash) and +# no crashpad — the static musl binaries are built without crashpad support. +set -e + +export PATH=$PATH:/usr/local/bin/:/usr/local/sbin/ + +trapeze() { + printf "\rFinishing container.." + exit 0 +} +trap trapeze INT + +if [ $# -eq 0 ]; then + echo "No arguments provided: specify either 'pktvisor-cli', 'pktvisor-reader' or 'pktvisord'. Try:" + echo "docker run netboxlabs/pktvisor:develop-alpine pktvisor-cli -h" + echo "docker run netboxlabs/pktvisor:develop-alpine pktvisor-reader --help" + echo "docker run netboxlabs/pktvisor:develop-alpine pktvisord --help" + exit 1 +fi + +BINARY="$1" +if [ "$BINARY" = 'pktvisor' ]; then + BINARY='pktvisor-cli' +fi +if [ "$BINARY" = 'pktvisor-pcap' ]; then + BINARY='pktvisor-reader' +fi + +if [ "$BINARY" = 'pktvisor-cli' ]; then + sleep 1 +fi + +if [ "$BINARY" = 'pktvisord' ]; then + cd /geo-db/ + if [ -f "asn.mmdb.gz" ]; then + gzip -d asn.mmdb.gz + gzip -d city.mmdb.gz + fi + cd / + while true; do + if [ ! -f "/var/run/pktvisord.pid" ]; then + nohup /run.sh "$@" & + sleep 2 + if [ -d "/nohup.out" ]; then + tail -f /nohup.out & + fi + else + PID=$(cat /var/run/pktvisord.pid) + if [ ! -d "/proc/$PID" ]; then + echo "$PID is not running" + rm /var/run/pktvisord.pid + exit 1 + fi + sleep 10 + fi + done +else + shift + exec "$BINARY" "$@" +fi diff --git a/docker/alpine/run.sh b/docker/alpine/run.sh new file mode 100644 index 000000000..a68be3186 --- /dev/null +++ b/docker/alpine/run.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Runtime launcher for pktvisord in the Alpine image. The static musl +# build excludes crashpad, so no --cp-* flags are passed. +shift +pktvisord \ + --default-geo-city "/geo-db/city.mmdb" \ + --default-geo-asn "/geo-db/asn.mmdb" \ + --default-service-registry "/iana/custom-iana.csv" \ + "$@" & +echo $! > /var/run/pktvisord.pid From 1115fd1424f20396798bade8a8de6e79a9a5cf02 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Mon, 18 May 2026 18:03:14 -0300 Subject: [PATCH 2/4] ci(docker): generate version.go and register QEMU for Alpine jobs Two review fixes for package-alpine: 1. Add the cmake configure step that expands `golang/pkg/client/ version.go.in` into `version.go` before staging it for the Go build. The repo only ships the .in template; without this step, `cp golang/pkg/client/version.go .` fails on a fresh checkout (matches the existing pattern in the pkvisor-cli job). 2. Add `docker/setup-qemu-action` before `docker/setup-buildx-action` so the aarch64 matrix entry can execute Dockerfile RUN steps under linux/arm64 emulation on the x86_64 runner. The same pattern is already used in build-develop.yml and build-release.yml. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_cross.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build_cross.yml b/.github/workflows/build_cross.yml index 525d76862..678a9be59 100644 --- a/.github/workflows/build_cross.yml +++ b/.github/workflows/build_cross.yml @@ -251,6 +251,10 @@ jobs: name: pktvisor-reader-linux-${{matrix.arch}}-static path: ./ + - name: Configure CMake to generate version.go + shell: bash + run: VERSION_ONLY=1 cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=Release -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./cmake/conan_provider.cmake + - name: Stage pktvisor sources for go build run: | mv src pktvisor-src @@ -273,6 +277,9 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a #v4.0.0 + - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0 From 2c4c1c2d9fccc1f2731b108663e8db86226431a9 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Mon, 18 May 2026 19:06:53 -0300 Subject: [PATCH 3/4] ci(docker): TEMP allow Alpine image push from this PR for validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Broadens the package-alpine and merge-alpine `if:` gates so the images get built and pushed to Docker Hub from the current PR run, letting us pull and test :develop-alpine before merging. To be reverted before merge — the inline comment above each gate documents the original condition. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_cross.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_cross.yml b/.github/workflows/build_cross.yml index 678a9be59..45441f6ec 100644 --- a/.github/workflows/build_cross.yml +++ b/.github/workflows/build_cross.yml @@ -225,7 +225,10 @@ jobs: name: alpine-${{matrix.arch}} needs: pkvisor runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + # TEMPORARY: also fire on this PR so we can validate the published image + # before merge. Revert to `github.event_name == 'push' && github.ref == 'refs/heads/develop'` + # before merging. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' || github.head_ref == 'chore/alpine-image' strategy: fail-fast: false matrix: @@ -311,7 +314,10 @@ jobs: merge-alpine: needs: package-alpine runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + # TEMPORARY: also fire on this PR so we can validate the published image + # before merge. Revert to `github.event_name == 'push' && github.ref == 'refs/heads/develop'` + # before merging. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' || github.head_ref == 'chore/alpine-image' steps: - name: Download digests uses: actions/download-artifact@v8 From 7a42660e89dfd37a060a6a2f36ee50ca72700413 Mon Sep 17 00:00:00 2001 From: Leo Parente <23251360+leoparente@users.noreply.github.com> Date: Mon, 18 May 2026 19:23:03 -0300 Subject: [PATCH 4/4] ci(docker): restore push-to-develop gate on Alpine image jobs Reverts the temporary `|| github.head_ref == 'chore/alpine-image'` clause used to publish the image from this PR for validation. package-alpine and merge-alpine now fire only on `push` to `refs/heads/develop`, as designed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build_cross.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_cross.yml b/.github/workflows/build_cross.yml index 45441f6ec..678a9be59 100644 --- a/.github/workflows/build_cross.yml +++ b/.github/workflows/build_cross.yml @@ -225,10 +225,7 @@ jobs: name: alpine-${{matrix.arch}} needs: pkvisor runs-on: ubuntu-latest - # TEMPORARY: also fire on this PR so we can validate the published image - # before merge. Revert to `github.event_name == 'push' && github.ref == 'refs/heads/develop'` - # before merging. - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' || github.head_ref == 'chore/alpine-image' + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' strategy: fail-fast: false matrix: @@ -314,10 +311,7 @@ jobs: merge-alpine: needs: package-alpine runs-on: ubuntu-latest - # TEMPORARY: also fire on this PR so we can validate the published image - # before merge. Revert to `github.event_name == 'push' && github.ref == 'refs/heads/develop'` - # before merging. - if: github.event_name == 'push' && github.ref == 'refs/heads/develop' || github.head_ref == 'chore/alpine-image' + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' steps: - name: Download digests uses: actions/download-artifact@v8