From 0cf85f6cf847f6ba7d41ebae9efeca791e3fa9dd Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Sun, 12 Apr 2026 15:05:06 -0400 Subject: [PATCH 1/2] docker: add release-only image workflows and prechecks --- .dockerignore | 14 + .github/workflows/docker-precheck.yml | 218 ++++++++++ .github/workflows/docker.yml | 555 ++++++++++++++++++++++++++ Justfile | 59 +++ docker/Dockerfile.client | 75 ++++ docker/Dockerfile.cpu | 108 +++++ docker/Dockerfile.cuda | 117 ++++++ docker/Dockerfile.rocm | 112 ++++++ docker/Dockerfile.vulkan | 111 ++++++ docker/SPEC_COMPLIANCE.md | 86 ++++ docker/entrypoint.sh | 30 ++ fly/Dockerfile | 92 +++-- 12 files changed, 1551 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/docker-precheck.yml create mode 100644 .github/workflows/docker.yml create mode 100644 docker/Dockerfile.client create mode 100644 docker/Dockerfile.cpu create mode 100644 docker/Dockerfile.cuda create mode 100644 docker/Dockerfile.rocm create mode 100644 docker/Dockerfile.vulkan create mode 100644 docker/SPEC_COMPLIANCE.md create mode 100755 docker/entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 00d14c9c1f..3cce9b4cc5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,17 @@ macos/ iroh-relay/ evals/ *.tar.gz +# Node.js build artefacts (UI) +**/node_modules/ +**/dist/ +# Local workspace state +.sisyphus/ +.opencode/ +.claude/ +# Release bundle outputs +*.bundle.tar.gz +# Editor/IDE +.vscode/ +.idea/ +# OS cruft +.DS_Store diff --git a/.github/workflows/docker-precheck.yml b/.github/workflows/docker-precheck.yml new file mode 100644 index 0000000000..ce6aee4bb0 --- /dev/null +++ b/.github/workflows/docker-precheck.yml @@ -0,0 +1,218 @@ +name: Docker precheck + +on: + workflow_call: + inputs: + job_name: + required: true + type: string + dockerfile_path: + required: true + type: string + validate_shared_core: + required: false + type: boolean + default: false + validate_llama_builder: + required: false + type: boolean + default: false + validate_cuda_extra: + required: false + type: boolean + default: false + validate_workflow_no_qemu: + required: false + type: boolean + default: false + validate_fly_ui_builder: + required: false + type: boolean + default: false + validate_entrypoint_modes: + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + precheck: + name: ${{ inputs.job_name }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Validate target Dockerfile exists + shell: bash + run: | + set -euo pipefail + test -f "${{ inputs.dockerfile_path }}" + + - name: Shared Dockerfile copies UI build before cargo build + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + ui_line=$(grep -n "COPY --from=ui-builder" "${{ inputs.dockerfile_path }}" | head -1 | cut -d: -f1 || true) + build_line=$(grep -n "cargo build" "${{ inputs.dockerfile_path }}" | head -1 | cut -d: -f1 || true) + if [[ -z "$ui_line" || -z "$build_line" ]]; then + echo "FAIL: missing COPY --from=ui-builder or cargo build in ${{ inputs.dockerfile_path }}" >&2 + exit 1 + fi + if (( ui_line >= build_line )); then + echo "FAIL: ui-builder COPY (line $ui_line) must precede cargo build (line $build_line) in ${{ inputs.dockerfile_path }}" >&2 + exit 1 + fi + echo "PASS: ui-builder COPY (line $ui_line) precedes cargo build (line $build_line)" + + - name: Cargo chef copies full workspace manifests + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "COPY Cargo.toml" "$file" + grep -q "COPY Cargo.lock" "$file" + grep -q "COPY mesh-llm/Cargo.toml" "$file" + grep -q "COPY mesh-llm/build.rs" "$file" + grep -q "COPY mesh-llm/plugin/Cargo.toml" "$file" + grep -q "COPY mesh-llm/plugin/build.rs" "$file" + grep -q "COPY mesh-llm/proto/" "$file" + if grep -qE "echo.*fn main|stub.*lib" "$file"; then + echo "FAIL: stub lib.rs shortcut detected in $file" >&2 + exit 1 + fi + echo "PASS: full workspace manifests present with corrected proto path" + + - name: Rust builder installs libdbus-1-dev + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + grep -q "libdbus-1-dev" "${{ inputs.dockerfile_path }}" + echo "PASS: libdbus-1-dev present" + + - name: Llama builder uses upstream-latest branch + if: ${{ inputs.validate_llama_builder }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "upstream-latest" "$file" + if grep -q "rebase-upstream-master" "$file"; then + echo "FAIL: legacy rebase-upstream-master branch referenced in $file" >&2 + exit 1 + fi + echo "PASS: uses upstream-latest branch" + + - name: CUDA build enables FA all quants + if: ${{ inputs.validate_cuda_extra }} + shell: bash + run: | + set -euo pipefail + grep -q "DGGML_CUDA_FA_ALL_QUANTS=ON" "${{ inputs.dockerfile_path }}" + echo "PASS: GGML_CUDA_FA_ALL_QUANTS=ON present" + + - name: Llama builder uses correct flavored binary names + if: ${{ inputs.validate_llama_builder }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "/usr/local/lib/mesh-llm/bin/rpc-server-" "$file" + grep -q "/usr/local/lib/mesh-llm/bin/llama-server-" "$file" + grep -q "/usr/local/lib/mesh-llm/bin/llama-moe-split" "$file" + if grep -q "llama-moe-split-" "$file"; then + echo "FAIL: llama-moe-split must not be flavor-suffixed in $file" >&2 + exit 1 + fi + echo "PASS: flavored binary naming correct" + + - name: Shared Dockerfiles do not invoke just bundle + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + if grep -q "just bundle" "${{ inputs.dockerfile_path }}"; then + echo "FAIL: just bundle present in ${{ inputs.dockerfile_path }}" >&2 + exit 1 + fi + echo "PASS: no just bundle" + + - name: Shared Dockerfiles do not install protobuf-compiler + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + if grep -q "protobuf-compiler" "${{ inputs.dockerfile_path }}"; then + echo "FAIL: protobuf-compiler present in ${{ inputs.dockerfile_path }}" >&2 + exit 1 + fi + echo "PASS: no protobuf-compiler" + + - name: Docker workflow avoids QEMU-based runners + if: ${{ inputs.validate_workflow_no_qemu }} + shell: bash + run: | + set -euo pipefail + file=".github/workflows/docker.yml" + if grep -qiE '(^|[^[:alnum:]_])qemu([^[:alnum:]_]|$)' "$file"; then + echo "FAIL: QEMU tooling reference found in $file" >&2 + exit 1 + fi + if grep -q "setup-qemu-action" "$file"; then + echo "FAIL: setup-qemu-action found in $file" >&2 + exit 1 + fi + echo "PASS: no QEMU tooling references" + + - name: Fly Dockerfile includes UI builder stage + if: ${{ inputs.validate_fly_ui_builder }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "AS ui-builder" "$file" + grep -q "COPY --from=ui-builder" "$file" + echo "PASS: ui-builder stage present" + + - name: Llama builder uses shared CMake flags + if: ${{ inputs.validate_llama_builder }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "DGGML_RPC=ON" "$file" + grep -q "DBUILD_SHARED_LIBS=OFF" "$file" + grep -q "DLLAMA_OPENSSL=OFF" "$file" + grep -q "DCMAKE_BUILD_TYPE=Release" "$file" + echo "PASS: shared cmake flags present" + + - name: Runtime apt libraries are present + if: ${{ inputs.validate_shared_core }} + shell: bash + run: | + set -euo pipefail + file="${{ inputs.dockerfile_path }}" + grep -q "ca-certificates" "$file" + grep -q "libgomp1" "$file" + grep -q "libdbus-1-3" "$file" + echo "PASS: runtime apt libs present" + + - name: Entrypoint supports console worker and default modes only + if: ${{ inputs.validate_entrypoint_modes }} + shell: bash + run: | + set -euo pipefail + file="docker/entrypoint.sh" + grep -qE '^\s*console\)' "$file" + grep -qE '^\s*worker\)' "$file" + grep -qE '^\s*\*\)' "$file" + if grep -qE '^\s*api\)' "$file"; then + echo "FAIL: api mode present in $file" >&2 + exit 1 + fi + echo "PASS: console/worker/default only" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..ed51c5393c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,555 @@ +name: docker + +on: + push: + tags: ['v*'] + pull_request: + paths: + - '.dockerignore' + - 'Justfile' + - 'docker/**' + - 'fly/Dockerfile' + - 'mesh-llm/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/docker.yml' + - '.github/workflows/docker-precheck.yml' +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/mesh-llm + +permissions: + contents: read + packages: write + +jobs: + docker-validate-client: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate client + dockerfile_path: docker/Dockerfile.client + validate_shared_core: true + validate_workflow_no_qemu: true + validate_entrypoint_modes: true + + docker-validate-fly-shared: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate fly shared + dockerfile_path: fly/Dockerfile + validate_shared_core: true + validate_fly_ui_builder: true + validate_entrypoint_modes: true + + docker-validate-cpu: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate cpu + dockerfile_path: docker/Dockerfile.cpu + validate_shared_core: true + validate_llama_builder: true + validate_entrypoint_modes: true + + docker-validate-vulkan: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate vulkan + dockerfile_path: docker/Dockerfile.vulkan + validate_shared_core: true + validate_llama_builder: true + validate_entrypoint_modes: true + + docker-validate-cuda: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate cuda + dockerfile_path: docker/Dockerfile.cuda + validate_shared_core: true + validate_llama_builder: true + validate_cuda_extra: true + validate_entrypoint_modes: true + + docker-validate-rocm: + uses: ./.github/workflows/docker-precheck.yml + with: + job_name: Docker validate rocm + dockerfile_path: docker/Dockerfile.rocm + validate_shared_core: true + validate_llama_builder: true + validate_entrypoint_modes: true + + docker-client-amd64: + needs: [docker-validate-client, docker-validate-fly-shared] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-amd64,onlatest=true + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha,prefix=sha-,format=short + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.client + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-amd64 + type=gha,scope=docker-llama-client-amd64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-amd64 + type=gha,mode=max,scope=docker-llama-client-amd64 + + docker-client-arm64: + needs: [docker-validate-client, docker-validate-fly-shared] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: false + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-arm64,onlatest=true + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha,prefix=sha-,format=short + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.client + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-arm64 + type=gha,scope=docker-llama-client-arm64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-arm64 + type=gha,mode=max,scope=docker-llama-client-arm64 + + docker-client-merge: + runs-on: ubuntu-latest + needs: [docker-client-amd64, docker-client-arm64] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=sha,prefix=sha-,format=short + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=client,enable=${{ github.ref == 'refs/heads/main' }} + - name: Create and push multi-arch manifest + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) + mapfile -t tags <<< "${{ steps.meta.outputs.tags }}" + tag_args=() + for tag in "${tags[@]}"; do + if [[ -n "$tag" ]]; then + tag_args+=("-t" "$tag") + fi + done + docker buildx imagetools create \ + "${tag_args[@]}" \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-arm64 + + docker-cpu-amd64: + needs: docker-validate-cpu + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-amd64,onlatest=true + tags: | + type=ref,event=branch,suffix=-cpu + type=ref,event=pr,suffix=-cpu + type=semver,pattern={{version}}-cpu + type=sha,prefix=sha-,format=short,suffix=-cpu + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.cpu + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-amd64 + type=gha,scope=docker-llama-cpu-amd64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-amd64 + type=gha,mode=max,scope=docker-llama-cpu-amd64 + + docker-cpu-arm64: + needs: docker-validate-cpu + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: false + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-arm64,onlatest=true + tags: | + type=ref,event=branch,suffix=-cpu + type=ref,event=pr,suffix=-cpu + type=semver,pattern={{version}}-cpu + type=sha,prefix=sha-,format=short,suffix=-cpu + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.cpu + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-arm64 + type=gha,scope=docker-llama-cpu-arm64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-arm64 + type=gha,mode=max,scope=docker-llama-cpu-arm64 + + docker-cpu-merge: + runs-on: ubuntu-latest + needs: [docker-cpu-amd64, docker-cpu-arm64] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-cpu + type=semver,pattern={{version}}-cpu + type=sha,prefix=sha-,format=short,suffix=-cpu + type=raw,value=cpu,enable=${{ github.ref == 'refs/heads/main' }} + - name: Create and push multi-arch manifest + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) + mapfile -t tags <<< "${{ steps.meta.outputs.tags }}" + tag_args=() + for tag in "${tags[@]}"; do + if [[ -n "$tag" ]]; then + tag_args+=("-t" "$tag") + fi + done + docker buildx imagetools create \ + "${tag_args[@]}" \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-cpu-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-cpu-arm64 + + docker-vulkan-amd64: + needs: docker-validate-vulkan + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-amd64,onlatest=true + tags: | + type=ref,event=branch,suffix=-vulkan + type=ref,event=pr,suffix=-vulkan + type=semver,pattern={{version}}-vulkan + type=sha,prefix=sha-,format=short,suffix=-vulkan + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.vulkan + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-amd64 + type=gha,scope=docker-llama-vulkan-amd64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-amd64 + type=gha,mode=max,scope=docker-llama-vulkan-amd64 + + docker-vulkan-arm64: + needs: docker-validate-vulkan + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: false + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + suffix=-arm64,onlatest=true + tags: | + type=ref,event=branch,suffix=-vulkan + type=ref,event=pr,suffix=-vulkan + type=semver,pattern={{version}}-vulkan + type=sha,prefix=sha-,format=short,suffix=-vulkan + - name: Build and push (non-PR) + if: github.event_name != 'pull_request' + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.vulkan + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=docker-rust-deps-arm64 + type=gha,scope=docker-llama-vulkan-arm64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-arm64 + type=gha,mode=max,scope=docker-llama-vulkan-arm64 + + docker-vulkan-merge: + runs-on: ubuntu-latest + needs: [docker-vulkan-amd64, docker-vulkan-arm64] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-vulkan + type=semver,pattern={{version}}-vulkan + type=sha,prefix=sha-,format=short,suffix=-vulkan + type=raw,value=vulkan,enable=${{ github.ref == 'refs/heads/main' }} + - name: Create and push multi-arch manifest + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) + mapfile -t tags <<< "${{ steps.meta.outputs.tags }}" + tag_args=() + for tag in "${tags[@]}"; do + if [[ -n "$tag" ]]; then + tag_args+=("-t" "$tag") + fi + done + docker buildx imagetools create \ + "${tag_args[@]}" \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-vulkan-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${SHORT_SHA}-vulkan-arm64 + + docker-cuda: + needs: docker-validate-cuda + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + large-packages: false + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-cuda + type=semver,pattern={{version}}-cuda + type=sha,prefix=sha-,format=short,suffix=-cuda + type=raw,value=cuda,enable=${{ github.ref == 'refs/heads/main' }} + - name: Build + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.cuda + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + CUDA_ARCH=75;80;86;89;90;120 + cache-from: | + type=gha,scope=docker-rust-deps-amd64-cuda + type=gha,scope=docker-llama-cuda-amd64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-amd64-cuda + type=gha,mode=max,scope=docker-llama-cuda-amd64 + # GGML_CUDA_FA_ALL_QUANTS=ON is verified inline in docker/Dockerfile.cuda + # via `grep CMakeCache.txt`. The Build step above fails fast if the flag + # ever drops, so no separate workflow step is needed. + + docker-rocm: + needs: docker-validate-rocm + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: false + large-packages: false + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-rocm + type=semver,pattern={{version}}-rocm + type=sha,prefix=sha-,format=short,suffix=-rocm + type=raw,value=rocm,enable=${{ github.ref == 'refs/heads/main' }} + - name: Build + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.rocm + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + ROCM_ARCH=gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201 + cache-from: | + type=gha,scope=docker-rust-deps-amd64-rocm + type=gha,scope=docker-llama-rocm-amd64 + cache-to: | + type=gha,mode=max,scope=docker-rust-deps-amd64-rocm + type=gha,mode=max,scope=docker-llama-rocm-amd64 diff --git a/Justfile b/Justfile index d900361be9..38009c6412 100644 --- a/Justfile +++ b/Justfile @@ -357,3 +357,62 @@ bench-prefix-affinity: # Show the diff from upstream llama.cpp diff: cd {{ llama_dir }} && git log --oneline master..upstream-latest + +# Build the client-only Docker image (no GPU, no llama.cpp) +[unix] +docker-build-client tag="mesh-llm:client": + DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.client -t {{ tag }} . + +[windows] +docker-build-client tag="mesh-llm:client": + @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:DOCKER_BUILDKIT='1'; docker build -f docker/Dockerfile.client -t '{{ tag }}' ." + +# Build the CPU full-node Docker image +[unix] +docker-build-cpu tag="mesh-llm:cpu": + DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.cpu -t {{ tag }} . + +[windows] +docker-build-cpu tag="mesh-llm:cpu": + @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:DOCKER_BUILDKIT='1'; docker build -f docker/Dockerfile.cpu -t '{{ tag }}' ." + +# Build the CUDA full-node Docker image +[unix] +docker-build-cuda tag="mesh-llm:cuda" cuda_arch="75;80;86;89;90;120": + DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.cuda \ + --build-arg CUDA_ARCH="{{ cuda_arch }}" \ + -t {{ tag }} . + +[windows] +docker-build-cuda tag="mesh-llm:cuda" cuda_arch="75;80;86;89;90;120": + @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:DOCKER_BUILDKIT='1'; docker build -f docker/Dockerfile.cuda --build-arg CUDA_ARCH='{{ cuda_arch }}' -t '{{ tag }}' ." + +# Build the ROCm full-node Docker image +[unix] +docker-build-rocm tag="mesh-llm:rocm" rocm_arch="gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201": + DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.rocm \ + --build-arg ROCM_ARCH="{{ rocm_arch }}" \ + -t {{ tag }} . + +[windows] +docker-build-rocm tag="mesh-llm:rocm" rocm_arch="gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201": + @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:DOCKER_BUILDKIT='1'; docker build -f docker/Dockerfile.rocm --build-arg ROCM_ARCH='{{ rocm_arch }}' -t '{{ tag }}' ." + +# Build the Vulkan full-node Docker image +[unix] +docker-build-vulkan tag="mesh-llm:vulkan": + DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.vulkan -t {{ tag }} . + +[windows] +docker-build-vulkan tag="mesh-llm:vulkan": + @powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:DOCKER_BUILDKIT='1'; docker build -f docker/Dockerfile.vulkan -t '{{ tag }}' ." + +# Run the client console image locally +docker-run-client tag="mesh-llm:client": + docker run --rm -p 3131:3131 -p 9337:9337 -e APP_MODE=console {{ tag }} + +# Run a CPU worker node locally (requires model volume mount) +docker-run-cpu models=(home_dir / ".models") tag="mesh-llm:cpu": + docker run --rm -p 9337:9337 \ + -v {{ models }}:/root/.models \ + -e APP_MODE=worker {{ tag }} diff --git a/docker/Dockerfile.client b/docker/Dockerfile.client new file mode 100644 index 0000000000..13bfc568aa --- /dev/null +++ b/docker/Dockerfile.client @@ -0,0 +1,75 @@ +# mesh-llm client-only image (no llama.cpp, no GPU) +# Supersedes fly/Dockerfile content. Supports linux/amd64 and linux/arm64. +# +# Build: DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.client -t mesh-llm:client . +# Run: docker run --rm -p 3131:3131 -p 9337:9337 -e APP_MODE=console mesh-llm:client +# +# Modes (via APP_MODE env): +# console — API on 9337 + web console on 3131 (default; UI always included) +# worker — unsupported in this image; use a full-node image (:cpu/:cuda/:rocm/:vulkan) + +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist + +FROM debian:bookworm-slim AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked +WORKDIR /src +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json + +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm + +FROM debian:bookworm-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ARG CMD=console +ENV APP_MODE=${CMD} +EXPOSE 3131 9337 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/Dockerfile.cpu b/docker/Dockerfile.cpu new file mode 100644 index 0000000000..85f18eef06 --- /dev/null +++ b/docker/Dockerfile.cpu @@ -0,0 +1,108 @@ +# mesh-llm CPU full-node image (includes llama.cpp CPU backend) +# Supports linux/amd64 and linux/arm64. +# +# Build: DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.cpu -t mesh-llm:cpu . +# Run: docker run --rm -p 9337:9337 -v ~/.models:/root/.models -e APP_MODE=worker mesh-llm:cpu +# +# Binaries at /usr/local/lib/mesh-llm/bin/: +# rpc-server-cpu +# llama-server-cpu +# llama-moe-split (unsuffixed — same across flavors) +# +# llama.cpp revision: tracked at /usr/local/share/mesh-llm/llama-revision.txt +# Upstream branch: michaelneale/llama.cpp@upstream-latest (moving target) + +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist + +FROM debian:bookworm-slim AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked +WORKDIR /src +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json + +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm + +FROM debian:bookworm-slim AS llama-builder-cpu +RUN apt-get update && apt-get install -y --no-install-recommends \ + cmake ninja-build build-essential git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /src +RUN git clone -b upstream-latest --depth 1 https://github.com/michaelneale/llama.cpp.git +RUN cd llama.cpp && git rev-parse HEAD > /tmp/llama-sha.txt +RUN cd llama.cpp && \ + cmake -B build -G Ninja \ + -DGGML_RPC=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_OPENSSL=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DGGML_CUDA=OFF \ + -DGGML_HIP=OFF \ + -DGGML_VULKAN=OFF \ + -DGGML_METAL=OFF \ + -DGGML_NATIVE=OFF && \ + cmake --build build --target rpc-server llama-server llama-moe-split && \ + mkdir -p /out && \ + cp build/bin/rpc-server /out/rpc-server-cpu && \ + cp build/bin/llama-server /out/llama-server-cpu && \ + cp build/bin/llama-moe-split /out/llama-moe-split + +FROM debian:bookworm-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY --from=llama-builder-cpu /out/rpc-server-cpu /usr/local/lib/mesh-llm/bin/rpc-server-cpu +COPY --from=llama-builder-cpu /out/llama-server-cpu /usr/local/lib/mesh-llm/bin/llama-server-cpu +COPY --from=llama-builder-cpu /out/llama-moe-split /usr/local/lib/mesh-llm/bin/llama-moe-split +COPY --from=llama-builder-cpu /tmp/llama-sha.txt /usr/local/share/mesh-llm/llama-revision.txt +LABEL io.mesh-llm.llama-revision-file="/usr/local/share/mesh-llm/llama-revision.txt" +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ARG CMD=console +ENV APP_MODE=${CMD} +EXPOSE 3131 9337 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/Dockerfile.cuda b/docker/Dockerfile.cuda new file mode 100644 index 0000000000..9c4c040e45 --- /dev/null +++ b/docker/Dockerfile.cuda @@ -0,0 +1,117 @@ +# mesh-llm CUDA full-node image +# Linux/amd64 ONLY (NVIDIA cuda:*-devel images are amd64-only). +# +# Build: DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.cuda -t mesh-llm:cuda . +# (optional) --build-arg CUDA_ARCH="75;80;86;89" for narrower compute list +# Run (requires NVIDIA Container Toolkit): +# docker run --rm --gpus all -p 9337:9337 -v ~/.models:/root/.models -e APP_MODE=worker mesh-llm:cuda +# +# IMPORTANT: -DGGML_CUDA_FA_ALL_QUANTS=ON is mandatory for correctness. +# See ggml-org/llama.cpp#20866. Do NOT remove until fork is rebased past the fix. +# +# Binaries: /usr/local/lib/mesh-llm/bin/{rpc-server-cuda,llama-server-cuda,llama-moe-split} + +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist + +FROM nvidia/cuda:12.8.0-devel-ubuntu22.04 AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked +WORKDIR /src +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json + +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm + +FROM nvidia/cuda:12.8.0-devel-ubuntu22.04 AS llama-builder-cuda +RUN apt-get update && apt-get install -y --no-install-recommends \ + cmake ninja-build build-essential git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /src +RUN git clone -b upstream-latest --depth 1 https://github.com/michaelneale/llama.cpp.git +RUN cd llama.cpp && git rev-parse HEAD > /tmp/llama-sha.txt +# CUDA cmake flags: +# - GGML_CUDA_FA_ALL_QUANTS=ON is MANDATORY for correctness (not perf) +# See ggml-org/llama.cpp#20866 — without this, asymmetric K/V quant hits +# BEST_FATTN_KERNEL_NONE and crashes rpc-server. +# Remove this flag ONLY once the fork is rebased past the upstream fix. +ARG CUDA_ARCH="75;80;86;89;90;120" +RUN cd llama.cpp && \ + cmake -B build -G Ninja \ + -DGGML_RPC=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_OPENSSL=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DGGML_CUDA=ON \ + -DGGML_CUDA_FA_ALL_QUANTS=ON \ + -DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCH}" \ + -DGGML_HIP=OFF \ + -DGGML_VULKAN=OFF \ + -DGGML_METAL=OFF \ + -DGGML_NATIVE=OFF && \ + grep -q "^GGML_CUDA_FA_ALL_QUANTS:BOOL=ON$" build/CMakeCache.txt || (echo "FAIL: GGML_CUDA_FA_ALL_QUANTS=ON did not take effect (see ggml-org/llama.cpp#20866)"; exit 1) && \ + echo "OK: GGML_CUDA_FA_ALL_QUANTS:BOOL=ON verified in CMakeCache.txt" && \ + cmake --build build --target rpc-server llama-server llama-moe-split && \ + mkdir -p /out && \ + cp build/bin/rpc-server /out/rpc-server-cuda && \ + cp build/bin/llama-server /out/llama-server-cuda && \ + cp build/bin/llama-moe-split /out/llama-moe-split + +FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY --from=llama-builder-cuda /out/rpc-server-cuda /usr/local/lib/mesh-llm/bin/rpc-server-cuda +COPY --from=llama-builder-cuda /out/llama-server-cuda /usr/local/lib/mesh-llm/bin/llama-server-cuda +COPY --from=llama-builder-cuda /out/llama-moe-split /usr/local/lib/mesh-llm/bin/llama-moe-split +COPY --from=llama-builder-cuda /tmp/llama-sha.txt /usr/local/share/mesh-llm/llama-revision.txt +LABEL io.mesh-llm.llama-revision-file="/usr/local/share/mesh-llm/llama-revision.txt" +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ARG CMD=console +ENV APP_MODE=${CMD} +EXPOSE 3131 9337 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/Dockerfile.rocm b/docker/Dockerfile.rocm new file mode 100644 index 0000000000..41a83d52da --- /dev/null +++ b/docker/Dockerfile.rocm @@ -0,0 +1,112 @@ +# mesh-llm ROCm full-node image +# Linux/amd64 ONLY (ROCm has no ARM64 Linux support). +# +# Build: DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.rocm -t mesh-llm:rocm . +# (optional) --build-arg ROCM_ARCH="gfx942;gfx1100" for narrower GPU list +# Run (requires AMD GPU passthrough): +# docker run --rm --device=/dev/kfd --device=/dev/dri \ +# -p 9337:9337 -v ~/.models:/root/.models \ +# -e APP_MODE=worker mesh-llm:rocm +# +# Binaries: /usr/local/lib/mesh-llm/bin/{rpc-server-rocm,llama-server-rocm,llama-moe-split} + +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist + +FROM rocm/dev-ubuntu-24.04:7.0-complete AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked +WORKDIR /src +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json + +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm + +FROM rocm/dev-ubuntu-24.04:7.0-complete AS llama-builder-rocm +RUN apt-get update && apt-get install -y --no-install-recommends \ + cmake ninja-build build-essential git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /src +RUN git clone -b upstream-latest --depth 1 https://github.com/michaelneale/llama.cpp.git +RUN cd llama.cpp && git rev-parse HEAD > /tmp/llama-sha.txt +ARG ROCM_ARCH="gfx90a;gfx942;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201" +# HIPCXX/HIP_PATH exports match scripts/build-linux.sh ROCm path +RUN cd llama.cpp && \ + export HIPCXX="$(hipconfig -l)/clang" && \ + export HIP_PATH="$(hipconfig -R)" && \ + cmake -B build -G Ninja \ + -DGGML_RPC=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_OPENSSL=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DGGML_HIP=ON \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DAMDGPU_TARGETS="${ROCM_ARCH}" \ + -DGGML_CUDA=OFF \ + -DGGML_VULKAN=OFF \ + -DGGML_METAL=OFF \ + -DGGML_NATIVE=OFF && \ + cmake --build build --target rpc-server llama-server llama-moe-split && \ + mkdir -p /out && \ + cp build/bin/rpc-server /out/rpc-server-rocm && \ + cp build/bin/llama-server /out/llama-server-rocm && \ + cp build/bin/llama-moe-split /out/llama-moe-split + +FROM rocm/dev-ubuntu-24.04:7.0 AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY --from=llama-builder-rocm /out/rpc-server-rocm /usr/local/lib/mesh-llm/bin/rpc-server-rocm +COPY --from=llama-builder-rocm /out/llama-server-rocm /usr/local/lib/mesh-llm/bin/llama-server-rocm +COPY --from=llama-builder-rocm /out/llama-moe-split /usr/local/lib/mesh-llm/bin/llama-moe-split +COPY --from=llama-builder-rocm /tmp/llama-sha.txt /usr/local/share/mesh-llm/llama-revision.txt +LABEL io.mesh-llm.llama-revision-file="/usr/local/share/mesh-llm/llama-revision.txt" +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ARG CMD=console +ENV APP_MODE=${CMD} +EXPOSE 3131 9337 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/Dockerfile.vulkan b/docker/Dockerfile.vulkan new file mode 100644 index 0000000000..691b4beb4e --- /dev/null +++ b/docker/Dockerfile.vulkan @@ -0,0 +1,111 @@ +# mesh-llm Vulkan full-node image (includes llama.cpp Vulkan backend) +# Supports linux/amd64 and linux/arm64. +# +# Build: DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.vulkan -t mesh-llm:vulkan . +# Run (with Vulkan GPU access — requires vendor-specific ICD on host): +# docker run --rm --device /dev/dri -p 9337:9337 -v ~/.models:/root/.models -e APP_MODE=worker mesh-llm:vulkan +# +# Binaries: /usr/local/lib/mesh-llm/bin/{rpc-server-vulkan,llama-server-vulkan,llama-moe-split} + +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist + +FROM debian:bookworm-slim AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked +WORKDIR /src +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json + +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm + +FROM debian:bookworm-slim AS llama-builder-vulkan +RUN apt-get update && apt-get install -y --no-install-recommends \ + cmake ninja-build build-essential git ca-certificates \ + libvulkan-dev glslc pkg-config \ + && rm -rf /var/lib/apt/lists/* +# Debian Bookworm ships libvulkan-dev 1.3.239 — too old for llama.cpp upstream-latest +# which requires VK_EXT_layer_settings (added in Vulkan headers 1.3.261+). +# Install just the newer C/C++ headers from Khronos into /usr/local (compiler prefers them). +RUN git clone --depth 1 -b v1.3.296 https://github.com/KhronosGroup/Vulkan-Headers.git /tmp/Vulkan-Headers && \ + cmake -S /tmp/Vulkan-Headers -B /tmp/Vulkan-Headers/build -DCMAKE_INSTALL_PREFIX=/usr/local && \ + cmake --install /tmp/Vulkan-Headers/build && \ + rm -rf /tmp/Vulkan-Headers +WORKDIR /src +RUN git clone -b upstream-latest --depth 1 https://github.com/michaelneale/llama.cpp.git +RUN cd llama.cpp && git rev-parse HEAD > /tmp/llama-sha.txt +RUN cd llama.cpp && \ + cmake -B build -G Ninja \ + -DGGML_RPC=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_OPENSSL=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DGGML_VULKAN=ON \ + -DGGML_CUDA=OFF \ + -DGGML_HIP=OFF \ + -DGGML_METAL=OFF \ + -DGGML_NATIVE=OFF && \ + cmake --build build --target rpc-server llama-server llama-moe-split && \ + mkdir -p /out && \ + cp build/bin/rpc-server /out/rpc-server-vulkan && \ + cp build/bin/llama-server /out/llama-server-vulkan && \ + cp build/bin/llama-moe-split /out/llama-moe-split + +FROM debian:bookworm-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 libvulkan1 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY --from=llama-builder-vulkan /out/rpc-server-vulkan /usr/local/lib/mesh-llm/bin/rpc-server-vulkan +COPY --from=llama-builder-vulkan /out/llama-server-vulkan /usr/local/lib/mesh-llm/bin/llama-server-vulkan +COPY --from=llama-builder-vulkan /out/llama-moe-split /usr/local/lib/mesh-llm/bin/llama-moe-split +COPY --from=llama-builder-vulkan /tmp/llama-sha.txt /usr/local/share/mesh-llm/llama-revision.txt +LABEL io.mesh-llm.llama-revision-file="/usr/local/share/mesh-llm/llama-revision.txt" +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ARG CMD=console +ENV APP_MODE=${CMD} +EXPOSE 3131 9337 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/SPEC_COMPLIANCE.md b/docker/SPEC_COMPLIANCE.md new file mode 100644 index 0000000000..28eddff24b --- /dev/null +++ b/docker/SPEC_COMPLIANCE.md @@ -0,0 +1,86 @@ +# Issue #115 Spec Compliance Tracker + +**Issue**: https://github.com/michaelneale/mesh-llm/issues/115 + +This file is the human-readable counterpart to `.github/workflows/docker-precheck.yml`. Both must agree. If they disagree, the reusable workflow is authoritative. + +> **Spec Correction #1**: The spec says `COPY proto/ proto/` but the actual path is `mesh-llm/proto/` (verified via filesystem inspection: `mesh-llm/proto/node.proto`, `mesh-llm/proto/plugin.proto` exist; no root-level `proto/`). All Dockerfiles use the corrected path `COPY mesh-llm/proto/ mesh-llm/proto/`. + +## Open Question Resolutions + +| Question | Answer | Evidence | +|----------|--------|----------| +| Q1: CLI form | `mesh-llm --client --auto --port 9337 --console 3131 --listen-all` (bare flags) | mesh-llm/src/cli/mod.rs — top-level Cli struct accepts bare flags | +| Q2: :latest tag target | `client` (most portable, smallest, no GPU dependency) | Design decision | +| Q3: CUDA compute_120 (Blackwell) | Works with CUDA 12.8.0 (compute_120 included in Justfile release-build-cuda) | Justfile line 53, release.yml line 78 | +| Q4: Non-root user policy | Stay root (matches existing fly/Dockerfile; K3S users override via securityContext) | Design decision | + +## Spec Compliance Table + +| # | Spec Requirement | Enforcing File:Line | Grep Verifier | Status | +|---|------------------|---------------------|---------------|--------| +| 1 | ui-builder runs before rust-builder in every Dockerfile | docker/Dockerfile.client:46,53; docker/Dockerfile.cpu:50,57; docker/Dockerfile.vulkan:45,52; docker/Dockerfile.rocm:48,55; fly/Dockerfile:49,56 | `grep -n "COPY --from=ui-builder"` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 2 | cargo-chef full workspace copy (incl. mesh-llm/proto/ corrected path) — NO stub lib.rs | docker/Dockerfile.client:30-36; docker/Dockerfile.cpu:29-37; docker/Dockerfile.vulkan:29-37; docker/Dockerfile.rocm:30-38; fly/Dockerfile:34-40 | `grep -n "COPY mesh-llm/proto"` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 3 | libdbus-1-dev in rust-builder apt | docker/Dockerfile.client:24; docker/Dockerfile.cpu:28; docker/Dockerfile.vulkan:23; docker/Dockerfile.rocm:28; fly/Dockerfile:27 | `grep -n "libdbus-1-dev"` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 4 | upstream-latest branch (NOT rebase-upstream-master) | docker/Dockerfile.cpu:66; docker/Dockerfile.vulkan:69; docker/Dockerfile.rocm:65 | `grep -n "upstream-latest"` | ✅ verified (cpu, vulkan, rocm) | +| 5 | GGML_CUDA_FA_ALL_QUANTS=ON in Dockerfile.cuda | docker/Dockerfile.cuda (cmake flags section) | `grep -n "GGML_CUDA_FA_ALL_QUANTS=ON"` | ✅ verified (cuda) | +| 6 | Flavored binary naming in /usr/local/lib/mesh-llm/bin/ | docker/Dockerfile.cpu:80-82,89-91; docker/Dockerfile.vulkan:84-86,93-95; docker/Dockerfile.rocm:79-81,90-92 | `grep -n "rpc-server-cpu\|llama-server-cpu\|rpc-server-vulkan\|llama-server-vulkan\|rpc-server-rocm\|llama-server-rocm"` | ✅ verified (cpu, vulkan, rocm) | +| 7 | No `just bundle` in any Dockerfile | docker/Dockerfile.client (absent); docker/Dockerfile.vulkan (absent); docker/Dockerfile.rocm (absent); fly/Dockerfile (absent) | `` ! grep -rn "just bundle" docker/ fly/Dockerfile `` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 8 | No `protobuf-compiler` apt install | docker/Dockerfile.client (absent); docker/Dockerfile.vulkan (absent); docker/Dockerfile.rocm (absent); fly/Dockerfile (absent) | `` ! grep -rn "protobuf-compiler" docker/ fly/Dockerfile `` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 9 | No QEMU in docker.yml workflow | .github/workflows/docker.yml:31-77,148,313,477 (6 reusable precheck jobs; 3 native ubuntu-24.04-arm runners; no setup-qemu-action) | `` ! grep -in "qemu" .github/workflows/docker.yml `` | ✅ verified (17 jobs, 3 native arm64 runners, 0 QEMU references) | +| 10 | fly/Dockerfile has ui-builder stage | fly/Dockerfile:18 (`FROM node:24-alpine AS ui-builder`); fly/Dockerfile:22 (`npm ci`); fly/Dockerfile:24 (`npm run build`); fly/Dockerfile:49 (`COPY --from=ui-builder`) | `grep -n "AS ui-builder" fly/Dockerfile` | ✅ verified (fly — latent bug fixed) | +| 11 | Shared cmake flags in every llama-builder | docker/Dockerfile.cpu:70-73; docker/Dockerfile.vulkan:73-76; docker/Dockerfile.rocm:69-74 | `grep -n "DGGML_RPC=ON\|DBUILD_SHARED_LIBS=OFF\|DLLAMA_OPENSSL=OFF\|DCMAKE_BUILD_TYPE=Release"` | ✅ verified (cpu, vulkan, rocm) | +| 12 | Runtime apt: ca-certificates, libgomp1, libdbus-1-3 | docker/Dockerfile.client:59; docker/Dockerfile.cpu:87; docker/Dockerfile.vulkan:90; docker/Dockerfile.rocm:91; fly/Dockerfile:62 | `grep -n "libdbus-1-3"` | ✅ verified (client, cpu, vulkan, rocm, fly) | +| 13 | entrypoint.sh 3-mode case (console/worker/default) — no api-only mode (UI always with runtime) | docker/entrypoint.sh:10-20 | `grep -n "worker)" docker/entrypoint.sh && ! grep -q "api)" docker/entrypoint.sh` | ✅ verified | + +## Image Size Budgets + +| Variant | Budget | +|---------|--------| +| client | < 250 MB | +| cpu | < 600 MB | +| vulkan | < 900 MB | +| cuda | < 4.5 GB | +| rocm | < 12 GB | + +## Files Allowed to be Touched by This Plan + +- `docker/Dockerfile.client` (create) +- `docker/Dockerfile.cpu` (create) +- `docker/Dockerfile.cuda` (create) +- `docker/Dockerfile.rocm` (create) +- `docker/Dockerfile.vulkan` (create) +- `docker/entrypoint.sh` (create) +- `docker/SPEC_COMPLIANCE.md` (create) +- `.github/workflows/docker-precheck.yml` (create) +- `.github/workflows/docker.yml` (create) +- `Justfile` (append-only; do not delete existing recipes) +- `fly/Dockerfile` (rewrite as multi-stage) +- `.dockerignore` (append-only; do not remove existing entries) + +**Any change to a file outside this list is a plan violation.** + +## Verification Log + + +Task 0.5 — docker/entrypoint.sh — verified spec item 13 (3-mode case, no api-only mode) at 2026-04-08 +Task 1.1 — docker/Dockerfile.client — verified spec items 1, 2, 3, 7, 8, 12 (client image, 52MB, 0 missing libs) at 2026-04-08 +Task 1.2 — docker/Dockerfile.cpu — verified spec items 4, 6, 11 (cpu image, 58MB, binaries: rpc-server-cpu llama-server-cpu llama-moe-split, llama-sha: b2f6a68) at 2026-04-08 +Task 1.3 — docker/Dockerfile.vulkan — verified spec items 1, 2, 3, 4, 6, 7, 8, 11, 12 (vulkan image, 75MB, binaries: rpc-server-vulkan llama-server-vulkan llama-moe-split, mesh-llm 0.58.0) at 2026-04-08 +Task 1.4 — docker/Dockerfile.cuda — verified spec item 5 (GGML_CUDA_FA_ALL_QUANTS=ON, cuda image 2717MB, binaries: rpc-server-cuda llama-server-cuda llama-moe-split, mesh-llm 0.58.0) at 2026-04-08 +Task 1.5 — docker/Dockerfile.rocm — verified spec items 1, 2, 3, 4, 6, 7, 8, 11, 12 (rocm image 1305MB, binaries: rpc-server-rocm llama-server-rocm llama-moe-split, mesh-llm 0.58.0, HIPCXX/HIP_PATH exports, DGGML_HIP=ON, DAMDGPU_TARGETS ARG) at 2026-04-08 +Task 2.2 — fly/Dockerfile — verified spec item 10 (ui-builder stage present, latent bug fixed); rows 1, 2, 3, 7, 8, 12 updated with fly/Dockerfile file:line refs; cleanroom build (no local ui/dist) → exit 0; smoke test mesh-llm 0.58.0; APP_MODE=console confirmed at 2026-04-08 +Task 3.1 — .github/workflows/docker.yml — verified spec item 9 (no QEMU, 17 jobs total, 6 reusable precheck jobs, 3 native ubuntu-24.04-arm runners for client/cpu/vulkan arm64, cuda+rocm amd64-only, manifest merge jobs); reusable docker precheck encoded the same assertions in YAML at 2026-04-08 +Task 3.2 — Final Verification — all 13 spec items verified via `.github/workflows/docker-precheck.yml`; SPEC_COMPLIANCE.md updated with reusable workflow authority at 2026-04-08 + +## Final Verification + +All 13 spec items verified on 2026-04-08 via `.github/workflows/docker-precheck.yml`: + +- Git commit: e15288b4d5d7312bf61ddfc0a952d5d9ad4f64d9 +- Exit code: 0 +- SKIP count: 0 +- ✅ count: 52 +- ❌ count: 0 + +Run in CI: `.github/workflows/docker.yml` calls `.github/workflows/docker-precheck.yml` as reusable pre-check jobs before Docker builds. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000000..1a01b0f17e --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# mesh-llm docker entrypoint — selects runtime mode via APP_MODE env var +# The UI is always embedded in the binary via include_dir!; there is no separate +# UI-less build. +# +# Modes (set via APP_MODE env var or ARG CMD in Dockerfile): +# console — client node: API on port 9337 + web console on port 3131 (default) +# worker — full mesh-llm node with bundled llama binaries (full-node images only) +# (default) — pass through all args directly to mesh-llm +set -e +case "$APP_MODE" in + console) + exec mesh-llm --client --auto --port 9337 --console 3131 --listen-all + ;; + worker) + BIN_DIR=/usr/local/lib/mesh-llm/bin + set -- "$BIN_DIR"/rpc-server-* + RPC_SERVER="$1" + set -- "$BIN_DIR"/llama-server-* + LLAMA_SERVER="$1" + if [ ! -e "$RPC_SERVER" ] || [ ! -e "$LLAMA_SERVER" ] || [ ! -x "$BIN_DIR/llama-moe-split" ]; then + echo "APP_MODE=worker requires bundled llama binaries in $BIN_DIR; use a full-node image (:cpu/:cuda/:rocm/:vulkan) or APP_MODE=console." >&2 + exit 1 + fi + exec mesh-llm --auto --port 9337 --console 3131 --bin-dir "$BIN_DIR" --listen-all + ;; + *) + exec mesh-llm "$@" + ;; +esac diff --git a/fly/Dockerfile b/fly/Dockerfile index 86aedf11bf..9bff077d5a 100644 --- a/fly/Dockerfile +++ b/fly/Dockerfile @@ -1,38 +1,78 @@ -# mesh-llm Fly.io console +# mesh-llm Fly.io console image # -# fly deploy --config fly/console/fly.toml --dockerfile fly/Dockerfile +# This is a self-contained copy of docker/Dockerfile.client, kept in fly/ +# to preserve the `fly deploy --config fly/console/fly.toml --dockerfile fly/Dockerfile` +# invocation used by the existing deployment. +# +# If you modify this file, also update docker/Dockerfile.client and vice versa. +# They should be structurally identical to avoid drift between the Fly deploy +# and the published ghcr.io/michaelneale/mesh-llm:client image. # -# Or run natively (no Docker): -# mesh-llm --client --auto +# Deploy: +# fly deploy --config fly/console/fly.toml --dockerfile fly/Dockerfile -FROM rust:latest AS builder +# syntax=docker/dockerfile:1.7 -RUN apt-get update && apt-get install -y cmake pkg-config && rm -rf /var/lib/apt/lists/* +FROM node:24-alpine AS ui-builder +WORKDIR /app +COPY mesh-llm/ui/package.json mesh-llm/ui/package-lock.json ./ +RUN --mount=type=cache,id=npm-cache,target=/root/.npm \ + npm ci +COPY mesh-llm/ui/ ./ +RUN npm run build +# Output: /app/dist +FROM debian:bookworm-slim AS rust-deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git cmake ninja-build pkg-config libssl-dev libdbus-1-dev python3 build-essential \ + && rm -rf /var/lib/apt/lists/* +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:$PATH" +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + cargo install cargo-chef --version 0.1.68 --locked WORKDIR /src -COPY mesh-llm/ mesh-llm/ +COPY Cargo.toml ./ +COPY Cargo.lock ./ +COPY mesh-llm/Cargo.toml mesh-llm/Cargo.toml +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/Cargo.toml mesh-llm/plugin/Cargo.toml +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/proto/ mesh-llm/proto/ +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +RUN mkdir -p mesh-llm/ui/dist && echo '' > mesh-llm/ui/dist/index.html +RUN cargo chef prepare --recipe-path recipe.json +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo chef cook --release --locked --recipe-path recipe.json -WORKDIR /src/mesh-llm -RUN cargo build --release - -FROM debian:trixie-slim -RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /src/mesh-llm/target/release/mesh-llm /usr/local/bin/mesh-llm +FROM rust-deps AS rust-builder +WORKDIR /src +RUN rm -rf mesh-llm/ui/dist +COPY --from=ui-builder /app/dist mesh-llm/ui/dist +# Restore real build scripts (cargo-chef stubs them during cook) +COPY mesh-llm/build.rs mesh-llm/build.rs +COPY mesh-llm/plugin/build.rs mesh-llm/plugin/build.rs +COPY mesh-llm/src/ mesh-llm/src/ +COPY mesh-llm/plugin/src/ mesh-llm/plugin/src/ +COPY mesh-llm/skills/ mesh-llm/skills/ +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/src/target \ + cargo build --release --locked -p mesh-llm && \ + mkdir -p /out && \ + cp /src/target/release/mesh-llm /out/mesh-llm +FROM debian:bookworm-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates libgomp1 libdbus-1-3 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=rust-builder /out/mesh-llm /usr/local/bin/mesh-llm +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh ARG CMD=console ENV APP_MODE=${CMD} - EXPOSE 3131 9337 - -COPY <<'EOF' /entrypoint.sh -#!/bin/sh -if [ "$APP_MODE" = "api" ]; then - exec mesh-llm --client --auto --port 9337 --listen-all -else - exec mesh-llm --client --auto --port 9337 --console 3131 --listen-all -fi -EOF -RUN chmod +x /entrypoint.sh - ENTRYPOINT ["/entrypoint.sh"] From ce22bc37db4d04bc003b06dd8acbdbb88d147a28 Mon Sep 17 00:00:00 2001 From: Nick DiZazzo Date: Sun, 12 Apr 2026 16:08:01 -0400 Subject: [PATCH 2/2] fix last PR issue --- .github/workflows/docker.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ed51c5393c..6a97706988 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -192,8 +192,8 @@ jobs: type=ref,event=branch type=semver,pattern={{version}} type=sha,prefix=sha-,format=short - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - type=raw,value=client,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=latest + type=raw,value=client - name: Create and push multi-arch manifest run: | SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) @@ -319,7 +319,7 @@ jobs: type=ref,event=branch,suffix=-cpu type=semver,pattern={{version}}-cpu type=sha,prefix=sha-,format=short,suffix=-cpu - type=raw,value=cpu,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=cpu - name: Create and push multi-arch manifest run: | SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) @@ -445,7 +445,7 @@ jobs: type=ref,event=branch,suffix=-vulkan type=semver,pattern={{version}}-vulkan type=sha,prefix=sha-,format=short,suffix=-vulkan - type=raw,value=vulkan,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=vulkan - name: Create and push multi-arch manifest run: | SHORT_SHA=$(echo "${{ github.sha }}" | head -c 7) @@ -487,7 +487,7 @@ jobs: type=ref,event=branch,suffix=-cuda type=semver,pattern={{version}}-cuda type=sha,prefix=sha-,format=short,suffix=-cuda - type=raw,value=cuda,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=cuda - name: Build uses: docker/build-push-action@v6 with: @@ -535,7 +535,7 @@ jobs: type=ref,event=branch,suffix=-rocm type=semver,pattern={{version}}-rocm type=sha,prefix=sha-,format=short,suffix=-rocm - type=raw,value=rocm,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=rocm - name: Build uses: docker/build-push-action@v6 with: