name: "docker: build latest container" on: push: tags: - '*' workflow_dispatch: inputs: source_ref: description: 'Git ref to build (branch, tag, or commit SHA)' required: true default: 'master' image_tag: description: 'Docker tag to publish (without variant suffix)' required: true default: 'latest' variant: description: 'Variant to build manually' required: true type: choice default: all options: - all - standard - large_disk publish: description: 'Publish images and manifests' required: true type: boolean default: false permissions: contents: read security-events: write jobs: setup: runs-on: ubuntu-latest outputs: variants: ${{ steps.set-variants.outputs.variants }} publish: ${{ steps.set-publish.outputs.publish }} steps: - name: Select variants for this run id: set-variants run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.variant }}" != "all" ]; then variants="[\"${{ github.event.inputs.variant }}\"]" else variants='["standard","large_disk"]' fi echo "variants=$variants" >> "$GITHUB_OUTPUT" - name: Select publish mode id: set-publish run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "publish=${{ github.event.inputs.publish }}" >> "$GITHUB_OUTPUT" else echo "publish=true" >> "$GITHUB_OUTPUT" fi # ── Pre-build Rust volume server binaries natively ────────────────── 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@v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.ref }} - 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@v5 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@v4 with: name: rust-volume-${{ matrix.arch }} path: | weed-volume-large-disk-${{ matrix.arch }} weed-volume-normal-${{ matrix.arch }} build: needs: [setup, build-rust-binaries] runs-on: ubuntu-latest strategy: matrix: platform: [amd64, arm64, arm, 386] variant: ${{ fromJSON(needs.setup.outputs.variants) }} steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.ref }} - name: Free Disk Space run: | echo "Available disk space before cleanup:" df -h # Remove pre-installed tools sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL # Clean package managers sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* # Clean Docker aggressively sudo docker system prune -af --volumes # Clean Go cache if it exists [ -d ~/.cache/go-build ] && rm -rf ~/.cache/go-build || true [ -d /go/pkg ] && rm -rf /go/pkg || true echo "Available disk space after cleanup:" df -h - name: Configure variant id: config run: | if [ "${{ matrix.variant }}" == "large_disk" ]; then echo "tag_suffix=_large_disk" >> $GITHUB_OUTPUT echo "build_args=TAGS=5BytesOffset" >> $GITHUB_OUTPUT echo "rust_variant=large-disk" >> $GITHUB_OUTPUT else echo "tag_suffix=" >> $GITHUB_OUTPUT echo "build_args=" >> $GITHUB_OUTPUT echo "rust_variant=normal" >> $GITHUB_OUTPUT fi - name: Download pre-built Rust binaries uses: actions/download-artifact@v4 with: pattern: rust-volume-* merge-multiple: true path: ./rust-bins - name: Place Rust binaries in Docker context run: | mkdir -p docker/weed-volume-prebuilt for arch in amd64 arm64; do src="./rust-bins/weed-volume-${{ steps.config.outputs.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: Docker meta id: docker_meta uses: docker/metadata-action@v6 with: images: | chrislusf/seaweedfs ghcr.io/chrislusf/seaweedfs tags: type=raw,value=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }},suffix=${{ steps.config.outputs.tag_suffix }} 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: matrix.platform != 'amd64' uses: docker/setup-qemu-action@v4 - name: Create BuildKit config run: | cat > /tmp/buildkitd.toml <> $GITHUB_OUTPUT else echo "tag_suffix=" >> $GITHUB_OUTPUT fi - name: Login to GHCR if: needs.setup.outputs.publish == 'true' uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Checkout for local scan build if: needs.setup.outputs.publish != 'true' uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.ref }} - name: Download pre-built Rust binaries for local scan if: needs.setup.outputs.publish != 'true' uses: actions/download-artifact@v4 with: pattern: rust-volume-* merge-multiple: true path: ./rust-bins - name: Place Rust binaries in Docker context for local scan if: needs.setup.outputs.publish != 'true' run: | rust_variant="normal" if [ "${{ matrix.variant }}" == "large_disk" ]; then rust_variant="large-disk" fi mkdir -p docker/weed-volume-prebuilt for arch in amd64 arm64; do src="./rust-bins/weed-volume-${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: Create BuildKit config for local scan build if: needs.setup.outputs.publish != 'true' run: | cat > /tmp/buildkitd.toml <> $GITHUB_OUTPUT else echo "tag_suffix=" >> $GITHUB_OUTPUT fi - name: Docker meta id: docker_meta uses: docker/metadata-action@v6 with: images: | chrislusf/seaweedfs ghcr.io/chrislusf/seaweedfs tags: type=raw,value=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }},suffix=${{ steps.config.outputs.tag_suffix }} - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Install crane run: | # Install crane for efficient multi-arch image copying 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: Create and push manifest run: | SUFFIX="${{ steps.config.outputs.tag_suffix }}" BASE_TAG="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }}" # Create manifest on GHCR first (no rate limits) echo "Creating GHCR manifest (no rate limits)..." docker buildx imagetools create -t ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-amd64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386 # Copy the complete multi-arch image from GHCR to Docker Hub # This only requires one pull from GHCR (no rate limit) and one push to Docker Hub echo "Copying manifest from GHCR to Docker Hub..." # Function to retry with exponential backoff for Docker Hub operations 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 } # Use crane or skopeo to copy, fallback to docker if not available if command -v crane &> /dev/null; then echo "Using crane to copy..." retry_with_backoff crane copy ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} elif command -v skopeo &> /dev/null; then echo "Using skopeo to copy..." retry_with_backoff skopeo copy --all docker://ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} docker://chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} else echo "Using docker buildx imagetools (pulling 4 images from Docker Hub)..." # Fallback: create manifest directly on Docker Hub (pulls from Docker Hub - rate limited) retry_with_backoff docker buildx imagetools create -t chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-amd64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386 fi