From 16fd1b90052d77ecd6c18695642e118e4f9c7e45 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 3 Apr 2026 15:24:22 +0000 Subject: [PATCH 1/2] Add GHCR Docker container build for multi-arch binaries Multi-stage Dockerfile (cargo-chef caching, ubuntu:24.04 runtime) packages all 130+ POSIX utilities. CI workflow builds natively on amd64 + arm64 runners, pushes multi-arch manifest to ghcr.io on main/tags. Co-Authored-By: Claude Opus 4.6 (1M context) --- .dockerignore | 21 +++++ .github/workflows/container.yml | 142 ++++++++++++++++++++++++++++++++ Dockerfile | 67 +++++++++++++++ docker-compose.yml | 8 ++ 4 files changed, 238 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/container.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f1f5d7e3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Build artifacts +target/ + +# Version control +.git/ +.gitignore + +# Documentation (not needed for build) +*.md + +# CI/CD +.github/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Misc +.DS_Store diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 00000000..4436e53f --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,142 @@ +name: container + +on: + pull_request: + workflow_run: + workflows: [Rust] + types: [completed] + branches: [main] + push: + tags: ['v*'] + +permissions: + contents: read + packages: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref || github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + if: | + github.event_name == 'pull_request' || + github.event_name == 'push' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + platform: linux/amd64 + arch: amd64 + - runner: ubuntu-24.04-arm + platform: linux/arm64 + arch: arm64 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha || '' }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: >- + type=image, + name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}, + push-by-digest=true, + name-canonical=true, + push=${{ github.event_name != 'pull_request' }} + cache-from: type=gha,scope=${{ matrix.arch }} + cache-to: type=gha,scope=${{ matrix.arch }},mode=max + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.event_name != 'pull_request' + needs: build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=edge,branch=main + type=sha,prefix=sha- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect \ + $(jq -cr '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON") diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..801d46e2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +# syntax=docker/dockerfile:1 + +# ============================================================================= +# Stage 1: Chef - Install cargo-chef for dependency caching +# ============================================================================= +FROM rust:bookworm AS chef +RUN cargo install cargo-chef +WORKDIR /app + +# ============================================================================= +# Stage 2: Planner - Generate dependency recipe +# ============================================================================= +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +# ============================================================================= +# Stage 3: Builder - Build all workspace binaries +# ============================================================================= +FROM chef AS builder + +# libclang-dev: required by bindgen (build-dep of process crate) +RUN apt-get update && apt-get install -y --no-install-recommends \ + libclang-dev \ + && rm -rf /var/lib/apt/lists/* + +# Build dependencies first (cached layer) +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json + +# Build all workspace binaries +COPY . . +RUN cargo build --release + +# Collect binaries into staging directory +RUN mkdir -p /app/staging/bin && \ + find target/release -maxdepth 1 -type f -executable \ + ! -name '*.d' ! -name 'build-script-*' \ + -exec cp {} /app/staging/bin/ \; + +# ============================================================================= +# Stage 4: Runtime - Minimal image with binaries +# ============================================================================= +FROM ubuntu:24.04 AS runtime + +LABEL org.opencontainers.image.source="https://github.com/rustcoreutils/posixutils-rs" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.description="Rust-native POSIX utilities (130+ commands)" +LABEL org.opencontainers.image.title="posixutils-rs" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd --create-home --shell /bin/bash posixutils + +COPY --from=builder /app/staging/bin/ /usr/local/bin/ + +# argv[0]-based symlinks (vi->ex, compress->uncompress/zcat) +RUN ln -sf vi /usr/local/bin/ex && \ + ln -sf compress /usr/local/bin/uncompress && \ + ln -sf compress /usr/local/bin/zcat + +USER posixutils +WORKDIR /home/posixutils + +CMD ["sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..04d73182 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + posixutils: + build: . + stdin_open: true + tty: true + volumes: + - .:/workspace:ro + working_dir: /workspace From ba5e3ee3ec79998799bbe3382ae59df568f51d53 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 3 Apr 2026 15:45:02 +0000 Subject: [PATCH 2/2] Dockerfile: pin cargo-chef version, fix symlink comment direction Co-Authored-By: Claude Opus 4.6 (1M context) --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 801d46e2..61d9da6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # Stage 1: Chef - Install cargo-chef for dependency caching # ============================================================================= FROM rust:bookworm AS chef -RUN cargo install cargo-chef +RUN cargo install --locked cargo-chef@0.1.77 WORKDIR /app # ============================================================================= @@ -56,7 +56,7 @@ RUN useradd --create-home --shell /bin/bash posixutils COPY --from=builder /app/staging/bin/ /usr/local/bin/ -# argv[0]-based symlinks (vi->ex, compress->uncompress/zcat) +# argv[0]-based symlinks (ex->vi, uncompress/zcat->compress) RUN ln -sf vi /usr/local/bin/ex && \ ln -sf compress /usr/local/bin/uncompress && \ ln -sf compress /usr/local/bin/zcat