Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
# Build the multi-platform container image and push it to both registries.
# BuildKit is used to generate SBOM and provenance metadata.
- name: Build and push (multi-registry, multi-platform)
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
push: true
Expand Down
92 changes: 66 additions & 26 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,30 +57,31 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Authenticate to Docker Hub so the workflow can push images to test.
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# Build the Docker image and load it locally
- name: Build Image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
push: false
outputs: type=oci,dest=image.tar
tags: runtimenode/test:pr-${{ github.event.pull_request.number }}
cache-to: type=gha,mode=max

# Save the built image as an artifact for the Test Image job
- name: Upload Docker Image Artifact
uses: actions/upload-artifact@v4
with:
name: docker-image-pr-${{ github.event.pull_request.number }}
path: image.tar

# Job to test the Docker image across multiple architectures
test-image:
name: Test Image
runs-on: ubuntu-24.04
needs: [lint, build-image]
env:
TEST_IMAGE: runtimenode/test:pr-${{ github.event.pull_request.number }}
TEST_IMAGE: test:pr-${{ github.event.pull_request.number }}
strategy:
fail-fast: true
matrix:
Expand All @@ -99,24 +100,37 @@ jobs:
- name: Set up QEMU
if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3

# Authenticate to Docker Hub so the workflow can test the image.
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

# Pull the Docker image artifact built in the previous job
- name: Pull Docker Image Artifact
run: docker pull --platform ${{ matrix.platform }} ${{ env.TEST_IMAGE }}
# Download the built Docker image artifact from the Build Image job
- name: Download Docker Image Artifact
uses: actions/download-artifact@v4
with:
name: docker-image-pr-${{ github.event.pull_request.number }}
path: artifacts

# Load the single-arch image into the local Docker daemon using Skopeo
- name: Extract single-arch image with Skopeo
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "${{ github.workspace }}:/workspace" \
quay.io/skopeo/stable:v1.22.0 \
copy \
--override-os linux \
--override-arch "$ARCH" \
oci-archive:/workspace/artifacts/image.tar \
docker-daemon:${{ env.TEST_IMAGE }}-$ARCH

# Smoke Test — verify node binary works
- name: Smoke Test — node --version
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
OUTPUT=$(docker run --rm --platform ${{ matrix.platform }} \
--entrypoint /usr/local/bin/node \
${{ env.TEST_IMAGE }} \
${{ env.TEST_IMAGE }}-$ARCH \
--version)

echo "Node.js version reported: $OUTPUT"
Expand All @@ -125,9 +139,11 @@ jobs:
# Integrity Test — ensure no shell is present
- name: Integrity Test — no shell present
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
if docker run --rm --platform ${{ matrix.platform }} \
--entrypoint /bin/sh \
${{ env.TEST_IMAGE }} \
${{ env.TEST_IMAGE }}-$ARCH \
-c "echo shell_found" 2>/dev/null; then
echo "❌ Shell was found inside the image. The distroless guarantee is broken."
exit 1
Expand All @@ -136,10 +152,12 @@ jobs:
# Integrity Test — ensure no package manager is present
- name: Integrity Test — no package manager present
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
for bin in /usr/bin/apk /usr/bin/apt /usr/bin/apt-get; do
if docker run --rm --platform ${{ matrix.platform }} \
--entrypoint "$bin" \
${{ env.TEST_IMAGE }} \
${{ env.TEST_IMAGE }}-$ARCH \
--version 2>/dev/null; then
echo "❌ Package manager found at $bin. The distroless guarantee is broken."
exit 1
Expand All @@ -149,19 +167,41 @@ jobs:
# Integrity Test — verify NODE_ENV is set to production
- name: Integrity Test — NODE_ENV is production
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
OUTPUT=$(docker run --rm --platform ${{ matrix.platform }} \
--entrypoint /usr/local/bin/node \
${{ env.TEST_IMAGE }} \
${{ env.TEST_IMAGE }}-$ARCH \
-e "process.stdout.write(process.env.NODE_ENV || '')")

[[ "$OUTPUT" == "production" ]] || exit 1

# Integrity Test — verify TZ is set to UTC
- name: Integrity Test — TZ is UTC
run: |
PLATFORM="${{ matrix.platform }}"
ARCH="${PLATFORM#linux/}"
OUTPUT=$(docker run --rm --platform ${{ matrix.platform }} \
--entrypoint /usr/local/bin/node \
${{ env.TEST_IMAGE }} \
${{ env.TEST_IMAGE }}-$ARCH \
-e "process.stdout.write(process.env.TZ || '')")

[[ "$OUTPUT" == "UTC" ]] || exit 1
[[ "$OUTPUT" == "UTC" ]] || exit 1

# Job to clean up the Docker image artifact after testing to free up storage space
artifact-clean-up:
name: Clean Up Artifacts
runs-on: ubuntu-24.04
needs: test-image
steps:
# Checkout the repository code for removing Artifact
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

# Remove the Docker image artifact to free up storage space
- name: Remove Docker Image Artifact
uses: geekyeggo/delete-artifact@v4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard artifact deletion when workflow token is read-only

This new cleanup step can fail legitimate PR checks from forks (and Dependabot) because those pull_request runs get a read-only GITHUB_TOKEN, while geekyeggo/delete-artifact requires write access to Actions and fails by default on permission errors. In that scenario, tests can pass but the workflow still ends in failure at cleanup, blocking external contributions unless this step is skipped/soft-failed for read-only contexts.

Useful? React with 👍 / 👎.

with:
name: docker-image-pr-${{ github.event.pull_request.number }}
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ All pull requests must:

## Node.js Version Bumps

Node.js version upgrades are tracked as versioned releases. Requests must include the official Alpine Node image tag (for example, `node:25.8.2-alpine3.23`) and are expected to trigger a semver release bump for the image.
Node.js version upgrades are tracked as versioned releases. Requests must include the official Alpine Node image tag (for example, `node:25.9.0-alpine3.23`) and are expected to trigger a semver release bump for the image.

## Documentation Standards

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Stage 1: Builder
# Extract binaries, libraries, and generate configuration files.
FROM node:25.8.2-alpine3.23 AS builder
FROM node:25.9.0-alpine3.23 AS builder

COPY --chmod=550 script.sh /
COPY --chmod=550 dependencies/requirements.txt /dependencies/
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Applications that need a different timezone can set `TZ` at runtime (timezone da
## Versioning and Tags

Tags follow the pattern:
- `v<image_semver>+node<node_version>` (example: `v2.1.3+node25.8.2`)
- `v<image_semver>+node<node_version>` (example: `v2.2.0+node25.9.0`)
- `latest` tracks the most recent release.

Check the GitHub Releases page for the current tag and Node.js version.
Expand Down Expand Up @@ -243,6 +243,16 @@ Runtime Node exists because of a small set of outstanding open-source projects t

[**actions/checkout**](https://github.com/actions/checkout) — GitHub Action used to check out the repository code in every workflow job.

[**actions/upload-artifact**](https://github.com/actions/upload-artifact) — GitHub Action used to persist build artifacts between workflow jobs during validation.

[**actions/download-artifact**](https://github.com/actions/download-artifact) — GitHub Action used to retrieve previously uploaded build artifacts for later verification steps.

[**geekyeggo/delete-artifact**](https://github.com/geekyeggo/delete-artifact) — GitHub Action used to clean up temporary workflow artifacts after validation completes.

[**chrnorm/deployment-action**](https://github.com/chrnorm/deployment-action) — GitHub Action used to create GitHub Deployment records for release and promotion workflow runs.

[**chrnorm/deployment-status**](https://github.com/chrnorm/deployment-status) — GitHub Action used to publish in-progress, success, and failure states back to the associated GitHub Deployment.

### Contributors

| | Name | GitHub | Role |
Expand Down
Loading