name: "docker: build all release containers (unified)" on: push: tags: - '*' workflow_dispatch: inputs: variant: description: 'Variant to build manually' required: true type: choice default: all options: - all - normal - large_disk - full - large_disk_full - rocksdb release_tag: description: 'Release tag to publish (e.g. 3.93)' required: true default: '' rocksdb_version: description: 'RocksDB git tag to use when variant=rocksdb' required: false default: 'v10.10.1' permissions: contents: read security-events: write env: RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || github.ref_name }} IMAGE: ghcr.io/chrislusf/seaweedfs # Limit concurrent builds to avoid rate limits concurrency: group: release-${{ github.ref }} cancel-in-progress: false jobs: # ── Pre-build Rust volume server binaries natively ────────────────── # Cross-compiles for amd64 and arm64 without QEMU, turning a 5-hour # emulated cargo build into ~15 minutes of native compilation. build-rust-binaries: runs-on: ubuntu-22.04 strategy: matrix: include: - target: x86_64-unknown-linux-musl arch: amd64 - target: aarch64-unknown-linux-musl arch: arm64 cross: true steps: - name: Checkout uses: actions/checkout@v7 - name: Install protobuf compiler run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Install musl tools (amd64) if: ${{ !matrix.cross }} run: sudo apt-get install -y musl-tools - name: Install cross-compilation tools (arm64) if: matrix.cross run: | sudo apt-get install -y gcc-aarch64-linux-gnu echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV" # Disable glibc fortify source — its __memcpy_chk etc. symbols don't exist in musl echo "CFLAGS_aarch64_unknown_linux_musl=-U_FORTIFY_SOURCE" >> "$GITHUB_ENV" - name: Cache cargo registry and target uses: actions/cache@v6 with: path: | ~/.cargo/registry ~/.cargo/git seaweed-volume/target key: rust-docker-${{ matrix.target }}-${{ hashFiles('seaweed-volume/Cargo.lock') }} restore-keys: | rust-docker-${{ matrix.target }}- - name: Build large-disk variant env: SEAWEEDFS_COMMIT: ${{ github.sha }} run: | cd seaweed-volume cargo build --release --target ${{ matrix.target }} cp target/${{ matrix.target }}/release/weed-volume ../weed-volume-large-disk-${{ matrix.arch }} - name: Build normal variant env: SEAWEEDFS_COMMIT: ${{ github.sha }} run: | cd seaweed-volume cargo build --release --target ${{ matrix.target }} --no-default-features cp target/${{ matrix.target }}/release/weed-volume ../weed-volume-normal-${{ matrix.arch }} - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: rust-volume-${{ matrix.arch }} path: | weed-volume-large-disk-${{ matrix.arch }} weed-volume-normal-${{ matrix.arch }} # One job per (variant, platform) on a native runner, pushed by digest; # the merge job stitches the digests into one multi-arch tag. build: needs: [build-rust-binaries] runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: # Normal volume - multi-arch - { variant: normal, tag_suffix: "", dockerfile: ./docker/Dockerfile.go_build, build_args: "", rust_variant: normal, platform: linux/amd64, arch: amd64, runner: ubuntu-latest, qemu: false } - { variant: normal, tag_suffix: "", dockerfile: ./docker/Dockerfile.go_build, build_args: "", rust_variant: normal, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm, qemu: false } - { variant: normal, tag_suffix: "", dockerfile: ./docker/Dockerfile.go_build, build_args: "", rust_variant: normal, platform: linux/arm/v7, arch: armv7, runner: ubuntu-latest, qemu: true } - { variant: normal, tag_suffix: "", dockerfile: ./docker/Dockerfile.go_build, build_args: "", rust_variant: normal, platform: linux/386, arch: i386, runner: ubuntu-latest, qemu: false } # Large disk - multi-arch - { variant: large_disk, tag_suffix: _large_disk, dockerfile: ./docker/Dockerfile.go_build, build_args: TAGS=5BytesOffset, rust_variant: large-disk, platform: linux/amd64, arch: amd64, runner: ubuntu-latest, qemu: false } - { variant: large_disk, tag_suffix: _large_disk, dockerfile: ./docker/Dockerfile.go_build, build_args: TAGS=5BytesOffset, rust_variant: large-disk, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm, qemu: false } - { variant: large_disk, tag_suffix: _large_disk, dockerfile: ./docker/Dockerfile.go_build, build_args: TAGS=5BytesOffset, rust_variant: large-disk, platform: linux/arm/v7, arch: armv7, runner: ubuntu-latest, qemu: true } - { variant: large_disk, tag_suffix: _large_disk, dockerfile: ./docker/Dockerfile.go_build, build_args: TAGS=5BytesOffset, rust_variant: large-disk, platform: linux/386, arch: i386, runner: ubuntu-latest, qemu: false } # Full tags - multi-arch - { variant: full, tag_suffix: _full, dockerfile: ./docker/Dockerfile.go_build, build_args: "TAGS=elastic,gocdk,rclone,sqlite,tarantool,tikv,ydb", rust_variant: normal, platform: linux/amd64, arch: amd64, runner: ubuntu-latest, qemu: false } - { variant: full, tag_suffix: _full, dockerfile: ./docker/Dockerfile.go_build, build_args: "TAGS=elastic,gocdk,rclone,sqlite,tarantool,tikv,ydb", rust_variant: normal, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm, qemu: false } # Large disk + full tags - multi-arch - { variant: large_disk_full, tag_suffix: _large_disk_full, dockerfile: ./docker/Dockerfile.go_build, build_args: "TAGS=5BytesOffset,elastic,gocdk,rclone,sqlite,tarantool,tikv,ydb", rust_variant: large-disk, platform: linux/amd64, arch: amd64, runner: ubuntu-latest, qemu: false } - { variant: large_disk_full, tag_suffix: _large_disk_full, dockerfile: ./docker/Dockerfile.go_build, build_args: "TAGS=5BytesOffset,elastic,gocdk,rclone,sqlite,tarantool,tikv,ydb", rust_variant: large-disk, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm, qemu: false } # RocksDB large disk - amd64 only - { variant: rocksdb, tag_suffix: _large_disk_rocksdb, dockerfile: ./docker/Dockerfile.rocksdb_large, build_args: "", rust_variant: large-disk, platform: linux/amd64, arch: amd64, runner: ubuntu-latest, qemu: false } steps: - name: Skip unselected variant if: github.event_name == 'workflow_dispatch' && github.event.inputs.variant != 'all' && github.event.inputs.variant != matrix.variant run: echo "Skipping ${{ matrix.variant }} (${{ matrix.platform }})" && exit 0 - name: Checkout if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant uses: actions/checkout@v7 - name: Download pre-built Rust binaries if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant uses: actions/download-artifact@v8 with: pattern: rust-volume-* merge-multiple: true path: ./rust-bins - name: Place Rust binaries in Docker context if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant run: | mkdir -p docker/weed-volume-prebuilt for arch in amd64 arm64; do src="./rust-bins/weed-volume-${{ matrix.rust_variant }}-${arch}" if [ -f "$src" ]; then cp "$src" "docker/weed-volume-prebuilt/weed-volume-${arch}" echo "Placed pre-built Rust binary for ${arch}" fi done ls -la docker/weed-volume-prebuilt/ - name: Free Disk Space if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* sudo docker system prune -af --volumes [ -d ~/.cache/go-build ] && rm -rf ~/.cache/go-build || true [ -d /go/pkg ] && rm -rf /go/pkg || true df -h - name: Docker meta if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant id: docker_meta uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE }} tags: type=raw,value=${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} flavor: latest=false labels: | org.opencontainers.image.title=seaweedfs org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast! org.opencontainers.image.vendor=Chris Lu - name: Set up QEMU if: (github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant) && matrix.qemu uses: docker/setup-qemu-action@v4.2.0 - name: Create BuildKit config if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant run: | cat > /tmp/buildkitd.toml <&2 sleep $timeout timeout=$((timeout * 2)) fi attempt=$((attempt + 1)) done echo "Command failed after $max_attempts attempts" >&2 return $exit_code } echo "Copying ${{ matrix.variant }} from GHCR to Docker Hub..." retry_with_backoff crane copy \ ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} \ chrislusf/seaweedfs:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} echo "Copied ${{ matrix.variant }} to Docker Hub" # Report-only trivy scan: uploads fixable HIGH/CRITICAL findings to GitHub # Security for visibility, but never blocks the release. Releases (including # `latest`) ship regardless — vulnerabilities are tracked, not gated, since # we sometimes need to publish through known findings (e.g. unfixed upstream # CVE, base-image lag). trivy-scan: runs-on: ubuntu-latest needs: [merge] if: github.event_name == 'push' continue-on-error: true strategy: fail-fast: false matrix: include: - source_suffix: "" variant: normal - source_suffix: _large_disk variant: large_disk steps: - name: Login to GHCR uses: docker/login-action@v4.4.0 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Trivy report (${{ matrix.variant }}) # Pin to SHA - mutable tags were compromised (GHSA-69fq-xp46-6x23) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: scan-type: image # Scan the multi-arch tag on GHCR (already pushed by the build job). # Trivy scans the runner's native platform; OS packages are identical # across architectures since they all share the same alpine base. image-ref: ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.source_suffix }} scanners: vuln vuln-type: os,library severity: HIGH,CRITICAL ignore-unfixed: true limit-severities-for-sarif: true format: sarif output: trivy-results.sarif exit-code: '0' - name: Upload Trivy scan results to GitHub Security if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: trivy-results.sarif category: trivy-${{ matrix.variant }} # Point `latest` (and `latest_large_disk`) at the just-released versioned # image. crane tag adds an extra tag to an existing manifest — no rebuild, # no QEMU, no separate workflow. Replaces the old container_latest.yml # rebuild that often failed or lagged behind the release. Independent of # trivy-scan: vuln findings are reported but do not block `latest`. tag-latest: runs-on: ubuntu-latest needs: [merge] if: github.event_name == 'push' strategy: matrix: include: - source_suffix: "" latest_tag: latest - source_suffix: _large_disk latest_tag: latest_large_disk steps: - name: Login to Docker Hub uses: docker/login-action@v4.4.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR uses: docker/login-action@v4.4.0 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Install crane run: | cd $(mktemp -d) curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz" | tar xz sudo mv crane /usr/local/bin/ crane version - name: Re-tag ${{ env.RELEASE_TAG }}${{ matrix.source_suffix }} as ${{ matrix.latest_tag }} run: | retry_with_backoff() { local max_attempts=5 local timeout=1 local attempt=1 local exit_code=0 while [ $attempt -le $max_attempts ]; do if "$@"; then return 0 else exit_code=$? fi if [ $attempt -lt $max_attempts ]; then echo "Attempt $attempt failed. Retrying in ${timeout}s..." >&2 sleep $timeout timeout=$((timeout * 2)) fi attempt=$((attempt + 1)) done echo "Command failed after $max_attempts attempts" >&2 return $exit_code } SRC_TAG="${{ env.RELEASE_TAG }}${{ matrix.source_suffix }}" DST_TAG="${{ matrix.latest_tag }}" echo "Tagging ${{ env.IMAGE }}:${SRC_TAG} as ${DST_TAG}" retry_with_backoff crane tag "${{ env.IMAGE }}:${SRC_TAG}" "${DST_TAG}" echo "Tagging chrislusf/seaweedfs:${SRC_TAG} as ${DST_TAG}" retry_with_backoff crane tag "chrislusf/seaweedfs:${SRC_TAG}" "${DST_TAG}" helm-release: runs-on: ubuntu-latest needs: [build] if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' permissions: contents: write pages: write steps: - uses: actions/checkout@v7 - name: Publish Helm charts uses: stefanprodan/helm-gh-pages@v1.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} charts_dir: k8s/charts target_dir: helm branch: gh-pages helm_version: "3.18.4"