Skip to content

Commit 048ce85

Browse files
authored
Merge pull request #574 from rustcoreutils/updates
Add GHCR Docker container build for multi-arch binaries
2 parents cc3f129 + ba5e3ee commit 048ce85

4 files changed

Lines changed: 238 additions & 0 deletions

File tree

.dockerignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Build artifacts
2+
target/
3+
4+
# Version control
5+
.git/
6+
.gitignore
7+
8+
# Documentation (not needed for build)
9+
*.md
10+
11+
# CI/CD
12+
.github/
13+
14+
# IDE
15+
.idea/
16+
.vscode/
17+
*.swp
18+
*.swo
19+
20+
# Misc
21+
.DS_Store

.github/workflows/container.yml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
name: container
2+
3+
on:
4+
pull_request:
5+
workflow_run:
6+
workflows: [Rust]
7+
types: [completed]
8+
branches: [main]
9+
push:
10+
tags: ['v*']
11+
12+
permissions:
13+
contents: read
14+
packages: write
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref || github.ref }}
18+
cancel-in-progress: true
19+
20+
env:
21+
REGISTRY: ghcr.io
22+
IMAGE_NAME: ${{ github.repository }}
23+
24+
jobs:
25+
build:
26+
if: |
27+
github.event_name == 'pull_request' ||
28+
github.event_name == 'push' ||
29+
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
30+
runs-on: ${{ matrix.runner }}
31+
timeout-minutes: 45
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
include:
36+
- runner: ubuntu-latest
37+
platform: linux/amd64
38+
arch: amd64
39+
- runner: ubuntu-24.04-arm
40+
platform: linux/arm64
41+
arch: arm64
42+
steps:
43+
- name: Checkout
44+
uses: actions/checkout@v4
45+
with:
46+
ref: ${{ github.event.workflow_run.head_sha || '' }}
47+
48+
- name: Set up Docker Buildx
49+
uses: docker/setup-buildx-action@v3
50+
51+
- name: Log in to GHCR
52+
if: github.event_name != 'pull_request'
53+
uses: docker/login-action@v3
54+
with:
55+
registry: ${{ env.REGISTRY }}
56+
username: ${{ github.actor }}
57+
password: ${{ secrets.GITHUB_TOKEN }}
58+
59+
- name: Extract Docker metadata
60+
id: meta
61+
uses: docker/metadata-action@v5
62+
with:
63+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
64+
65+
- name: Build and push by digest
66+
id: build
67+
uses: docker/build-push-action@v6
68+
with:
69+
context: .
70+
platforms: ${{ matrix.platform }}
71+
labels: ${{ steps.meta.outputs.labels }}
72+
outputs: >-
73+
type=image,
74+
name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},
75+
push-by-digest=true,
76+
name-canonical=true,
77+
push=${{ github.event_name != 'pull_request' }}
78+
cache-from: type=gha,scope=${{ matrix.arch }}
79+
cache-to: type=gha,scope=${{ matrix.arch }},mode=max
80+
81+
- name: Export digest
82+
if: github.event_name != 'pull_request'
83+
run: |
84+
mkdir -p /tmp/digests
85+
digest="${{ steps.build.outputs.digest }}"
86+
touch "/tmp/digests/${digest#sha256:}"
87+
88+
- name: Upload digest
89+
if: github.event_name != 'pull_request'
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: digests-${{ matrix.arch }}
93+
path: /tmp/digests/*
94+
if-no-files-found: error
95+
retention-days: 1
96+
97+
merge:
98+
runs-on: ubuntu-latest
99+
timeout-minutes: 10
100+
if: github.event_name != 'pull_request'
101+
needs: build
102+
steps:
103+
- name: Download digests
104+
uses: actions/download-artifact@v4
105+
with:
106+
path: /tmp/digests
107+
pattern: digests-*
108+
merge-multiple: true
109+
110+
- name: Set up Docker Buildx
111+
uses: docker/setup-buildx-action@v3
112+
113+
- name: Log in to GHCR
114+
uses: docker/login-action@v3
115+
with:
116+
registry: ${{ env.REGISTRY }}
117+
username: ${{ github.actor }}
118+
password: ${{ secrets.GITHUB_TOKEN }}
119+
120+
- name: Extract Docker metadata
121+
id: meta
122+
uses: docker/metadata-action@v5
123+
with:
124+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
125+
tags: |
126+
type=edge,branch=main
127+
type=sha,prefix=sha-
128+
type=semver,pattern={{version}}
129+
type=semver,pattern={{major}}.{{minor}}
130+
type=semver,pattern={{major}}
131+
132+
- name: Create manifest list and push
133+
working-directory: /tmp/digests
134+
run: |
135+
docker buildx imagetools create \
136+
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
137+
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
138+
139+
- name: Inspect image
140+
run: |
141+
docker buildx imagetools inspect \
142+
$(jq -cr '.tags[0]' <<< "$DOCKER_METADATA_OUTPUT_JSON")

Dockerfile

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# =============================================================================
4+
# Stage 1: Chef - Install cargo-chef for dependency caching
5+
# =============================================================================
6+
FROM rust:bookworm AS chef
7+
RUN cargo install --locked cargo-chef@0.1.77
8+
WORKDIR /app
9+
10+
# =============================================================================
11+
# Stage 2: Planner - Generate dependency recipe
12+
# =============================================================================
13+
FROM chef AS planner
14+
COPY . .
15+
RUN cargo chef prepare --recipe-path recipe.json
16+
17+
# =============================================================================
18+
# Stage 3: Builder - Build all workspace binaries
19+
# =============================================================================
20+
FROM chef AS builder
21+
22+
# libclang-dev: required by bindgen (build-dep of process crate)
23+
RUN apt-get update && apt-get install -y --no-install-recommends \
24+
libclang-dev \
25+
&& rm -rf /var/lib/apt/lists/*
26+
27+
# Build dependencies first (cached layer)
28+
COPY --from=planner /app/recipe.json recipe.json
29+
RUN cargo chef cook --release --recipe-path recipe.json
30+
31+
# Build all workspace binaries
32+
COPY . .
33+
RUN cargo build --release
34+
35+
# Collect binaries into staging directory
36+
RUN mkdir -p /app/staging/bin && \
37+
find target/release -maxdepth 1 -type f -executable \
38+
! -name '*.d' ! -name 'build-script-*' \
39+
-exec cp {} /app/staging/bin/ \;
40+
41+
# =============================================================================
42+
# Stage 4: Runtime - Minimal image with binaries
43+
# =============================================================================
44+
FROM ubuntu:24.04 AS runtime
45+
46+
LABEL org.opencontainers.image.source="https://github.com/rustcoreutils/posixutils-rs"
47+
LABEL org.opencontainers.image.licenses="MIT"
48+
LABEL org.opencontainers.image.description="Rust-native POSIX utilities (130+ commands)"
49+
LABEL org.opencontainers.image.title="posixutils-rs"
50+
51+
RUN apt-get update && apt-get install -y --no-install-recommends \
52+
ca-certificates \
53+
&& rm -rf /var/lib/apt/lists/*
54+
55+
RUN useradd --create-home --shell /bin/bash posixutils
56+
57+
COPY --from=builder /app/staging/bin/ /usr/local/bin/
58+
59+
# argv[0]-based symlinks (ex->vi, uncompress/zcat->compress)
60+
RUN ln -sf vi /usr/local/bin/ex && \
61+
ln -sf compress /usr/local/bin/uncompress && \
62+
ln -sf compress /usr/local/bin/zcat
63+
64+
USER posixutils
65+
WORKDIR /home/posixutils
66+
67+
CMD ["sh"]

docker-compose.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
posixutils:
3+
build: .
4+
stdin_open: true
5+
tty: true
6+
volumes:
7+
- .:/workspace:ro
8+
working_dir: /workspace

0 commit comments

Comments
 (0)