diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml
index e4494ca..f70f7b4 100644
--- a/.gitea/workflows/build.yaml
+++ b/.gitea/workflows/build.yaml
@@ -2,7 +2,7 @@ name: Build and Push .NET Images
on:
release:
- types: [published]
+ types: [published, prereleased]
workflow_dispatch:
schedule:
# Weekly rebuild for CVE patches (Sunday 6am UTC)
@@ -24,7 +24,18 @@ jobs:
matrix: ${{ steps.discover.outputs.matrix }}
lts_major: ${{ steps.discover.outputs.lts_major }}
sts_major: ${{ steps.discover.outputs.sts_major }}
+ tag_suffix: ${{ steps.suffix.outputs.tag_suffix }}
steps:
+ - name: Determine tag suffix
+ id: suffix
+ run: |
+ if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
+ echo "tag_suffix=-test" >> $GITHUB_OUTPUT
+ echo "Prerelease detected — tags will use -test suffix"
+ else
+ echo "tag_suffix=" >> $GITHUB_OUTPUT
+ fi
+
- name: Discover supported .NET versions
id: discover
run: |
@@ -64,6 +75,7 @@ jobs:
env:
LTS_MAJOR: ${{ needs.discover.outputs.lts_major }}
STS_MAJOR: ${{ needs.discover.outputs.sts_major }}
+ TAG_SUFFIX: ${{ needs.discover.outputs.tag_suffix }}
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -152,12 +164,12 @@ jobs:
cp dockerfiles/runtime.Dockerfile context-runtime/Dockerfile
# Determine tags
- TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}"
+ TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}"
if [ "$MAJOR" = "$LTS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}"
fi
if [ "$MAJOR" = "$STS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}"
fi
echo "Building $VARIANT with tags: $TAGS"
@@ -184,12 +196,12 @@ jobs:
done
cp dockerfiles/runtime-invariant.Dockerfile context-runtime-invariant/Dockerfile
- TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}"
+ TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}"
if [ "$MAJOR" = "$LTS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}"
fi
if [ "$MAJOR" = "$STS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}"
fi
echo "Building $VARIANT with tags: $TAGS"
@@ -216,12 +228,12 @@ jobs:
done
cp dockerfiles/sdk.Dockerfile context-sdk/Dockerfile
- TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}"
+ TAGS="-t $DOCKER_IMAGE:${VARIANT}-${MAJOR}${TAG_SUFFIX} -t $DOCKER_IMAGE:${VARIANT}-${VERSION}${TAG_SUFFIX}"
if [ "$MAJOR" = "$LTS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-lts${TAG_SUFFIX}"
fi
if [ "$MAJOR" = "$STS_MAJOR" ]; then
- TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts"
+ TAGS="$TAGS -t $DOCKER_IMAGE:${VARIANT}-sts${TAG_SUFFIX}"
fi
echo "Building $VARIANT with tags: $TAGS"
@@ -240,6 +252,6 @@ jobs:
MAJOR="${{ matrix.major }}"
for VARIANT in runtime runtime-invariant sdk; do
echo "::group::Scout scan: ${VARIANT}-${MAJOR}"
- docker scout cves ${{ env.DOCKER_IMAGE }}:${VARIANT}-${MAJOR} --only-severity critical,high || true
+ docker scout cves ${{ env.DOCKER_IMAGE }}:${VARIANT}-${MAJOR}${TAG_SUFFIX} --only-severity critical,high || true
echo "::endgroup::"
done
diff --git a/DOCKERHUB.md b/DOCKERHUB.md
new file mode 100644
index 0000000..c126997
--- /dev/null
+++ b/DOCKERHUB.md
@@ -0,0 +1,119 @@
+# .NET Docker Images
+
+
+
+
+Minimal .NET Docker images for production and CI/CD. Built on [Wolfi](https://wolfi.dev), a security-focused Linux distribution designed for containers. All supported (non-EOL) .NET versions are rebuilt automatically.
+
+## Variants
+
+- `runtime` - ASP.NET Core runtime with ICU/globalization support
+- `runtime-invariant` - ASP.NET Core runtime without ICU (smallest, invariant mode)
+- `sdk` - .NET SDK with bash, git, curl for building apps
+
+All variants support `linux/amd64` and `linux/arm64`.
+
+## Why Wolfi?
+
+[Wolfi](https://wolfi.dev) is a lightweight Linux distribution built specifically for containers. It provides:
+
+- **Minimal footprint** - Only essential packages, nothing extra
+- **Daily security updates** - Patches applied quickly
+- **Designed for containers** - No legacy cruft from traditional distros
+
+## Features
+
+- **Lightweight** - Optimized for fast CI/CD pulls
+- **Secure** - Built on Wolfi with continuous vulnerability scanning
+- **Multi-arch** - Supports both `linux/amd64` and `linux/arm64`
+- **Non-root** - Runtime images run as unprivileged user (UID 65532)
+- **Supply chain security** - SBOM and SLSA provenance attestations included
+- **EOL-aware** - Versions are automatically dropped when they reach end-of-life
+
+## Dockerfile Examples
+
+### Web API
+
+```dockerfile
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
+
+WORKDIR /source
+COPY . .
+WORKDIR /source/MyApp.Api
+
+ARG TARGETARCH
+RUN case "$TARGETARCH" in \
+ amd64) ARCH=x64 ;; \
+ arm64) ARCH=arm64 ;; \
+ *) ARCH=$TARGETARCH ;; \
+ esac && \
+ dotnet publish -a $ARCH --self-contained false -o /app
+
+FROM svrnty/dotnet:runtime-invariant-10 AS final
+WORKDIR /app
+COPY --from=build /app .
+USER 65532
+EXPOSE 8080
+ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
+```
+
+### Worker service
+
+```dockerfile
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-lts AS build
+
+WORKDIR /source
+COPY . .
+
+ARG TARGETARCH
+RUN case "$TARGETARCH" in \
+ amd64) ARCH=x64 ;; \
+ arm64) ARCH=arm64 ;; \
+ *) ARCH=$TARGETARCH ;; \
+ esac && \
+ dotnet publish MyWorker -a $ARCH --self-contained false -o /app
+
+FROM svrnty/dotnet:runtime-invariant-lts AS final
+WORKDIR /app
+COPY --from=build /app .
+USER 65532
+ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyWorker.dll"]
+```
+
+## CI/CD (Gitea/GitHub Actions)
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container:
+ image: svrnty/dotnet:sdk-lts
+ steps:
+ - uses: actions/checkout@v4
+ - run: dotnet restore
+ - run: dotnet test --no-restore
+ - run: dotnet publish -c Release -o /app
+```
+
+## Tags
+
+- `-` - Latest patch for a major version (e.g., `runtime-10`)
+- `-` - Exact version pin (e.g., `runtime-10.0.2`, `sdk-10.0.102`)
+- `-lts` - Floating tag for the highest active LTS
+- `-sts` - Floating tag for the highest active STS
+
+## Automatic Updates
+
+Images are automatically rebuilt when:
+
+- New .NET patch versions are released (daily check)
+- Base image security updates are available (weekly rebuild)
+- A .NET version reaches EOL, it is automatically excluded
+
+Every build is scanned and includes supply chain attestations (SBOM, SLSA provenance).
+
+## License
+
+MIT
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..31a55a1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,161 @@
+# .NET Docker Images
+
+
+
+
+
+
+
+Minimal .NET Docker images for production and CI/CD. Built on [Wolfi](https://wolfi.dev), a security-focused Linux distribution designed for containers. All supported (non-EOL) .NET versions are rebuilt automatically.
+
+## Images
+
+| Variant | Use Case | .NET 10 (LTS) | .NET 9 (STS) | .NET 8 | Arch |
+|---------|----------|----------------|---------------|--------|------|
+|
| Production web apps | `runtime-10` `runtime-lts` | `runtime-9` `runtime-sts` | `runtime-8` |
|
+|
| Minimal production (UTC-only) | `runtime-invariant-10` `runtime-invariant-lts` | `runtime-invariant-9` `runtime-invariant-sts` | `runtime-invariant-8` |
|
+|
| Building .NET apps | `sdk-10` `sdk-lts` | `sdk-9` `sdk-sts` | `sdk-8` |
|
+
+## Variants
+
+| Variant | Includes | Shell | ICU/Globalization | User |
+|---------|----------|-------|-------------------|------|
+| **runtime** | ASP.NET Core runtime | No | Yes | 65532 (nonroot) |
+| **runtime-invariant** | ASP.NET Core runtime | No | No (invariant mode) | 65532 (nonroot) |
+| **sdk** | .NET SDK + bash, git, curl | Yes (bash) | Yes | root |
+
+- **runtime** - Full globalization support (ICU + tzdata). Use this for apps that need locale-aware formatting, time zones, or culture-specific behavior.
+- **runtime-invariant** - No ICU or tzdata. Smallest image size. Use this for APIs that only need UTC and ordinal string comparison.
+- **sdk** - Everything needed to build .NET apps. Runs as root so `dotnet restore` can write to global caches.
+
+## Why Wolfi?
+
+[Wolfi](https://wolfi.dev) is a lightweight Linux distribution built specifically for containers. It provides:
+
+- **Minimal footprint** - Only essential packages, nothing extra
+- **Daily security updates** - Patches applied quickly
+- **Designed for containers** - No legacy cruft from traditional distros
+
+## Features
+
+- **Lightweight** - Optimized for fast CI/CD pulls
+- **Secure** - Built on Wolfi with continuous vulnerability scanning
+- **Multi-arch** - Supports both `linux/amd64` and `linux/arm64`
+- **Non-root** - Runtime images run as unprivileged user (UID 65532)
+- **Supply chain security** - SBOM and SLSA provenance attestations included
+- **EOL-aware** - Versions are automatically dropped when they reach end-of-life
+
+## Dockerfile Examples
+
+### Web API (recommended)
+
+```dockerfile
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
+
+WORKDIR /source
+COPY . .
+WORKDIR /source/MyApp.Api
+
+ARG TARGETARCH
+RUN case "$TARGETARCH" in \
+ amd64) ARCH=x64 ;; \
+ arm64) ARCH=arm64 ;; \
+ *) ARCH=$TARGETARCH ;; \
+ esac && \
+ dotnet publish -a $ARCH --self-contained false -o /app
+
+FROM svrnty/dotnet:runtime-invariant-10 AS final
+WORKDIR /app
+COPY --from=build /app .
+USER 65532
+EXPOSE 8080
+ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
+```
+
+### Web API with globalization (ICU)
+
+```dockerfile
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-lts AS build
+
+WORKDIR /source
+COPY . .
+
+ARG TARGETARCH
+RUN case "$TARGETARCH" in \
+ amd64) ARCH=x64 ;; \
+ arm64) ARCH=arm64 ;; \
+ *) ARCH=$TARGETARCH ;; \
+ esac && \
+ dotnet publish MyApp.sln -a $ARCH --self-contained false -o /app
+
+FROM svrnty/dotnet:runtime-lts AS final
+WORKDIR /app
+COPY --from=build /app .
+USER 65532
+EXPOSE 8080
+ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.dll"]
+```
+
+### Worker service
+
+```dockerfile
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
+
+WORKDIR /source
+COPY . .
+
+ARG TARGETARCH
+RUN case "$TARGETARCH" in \
+ amd64) ARCH=x64 ;; \
+ arm64) ARCH=arm64 ;; \
+ *) ARCH=$TARGETARCH ;; \
+ esac && \
+ dotnet publish MyWorker -a $ARCH --self-contained false -o /app
+
+FROM svrnty/dotnet:runtime-invariant-10 AS final
+WORKDIR /app
+COPY --from=build /app .
+USER 65532
+ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyWorker.dll"]
+```
+
+## CI/CD (Gitea/GitHub Actions)
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ container:
+ image: svrnty/dotnet:sdk-lts
+ steps:
+ - uses: actions/checkout@v4
+ - run: dotnet restore
+ - run: dotnet test --no-restore
+ - run: dotnet publish -c Release -o /app
+```
+
+## Tags
+
+Each variant is tagged with:
+
+- `-` - Latest patch for a major version (e.g., `runtime-10`)
+- `-` - Exact version pin (e.g., `runtime-10.0.2`, `sdk-10.0.102`)
+- `-lts` - Floating tag pointing to the highest active LTS (currently .NET 10)
+- `-sts` - Floating tag pointing to the highest active STS (currently .NET 9)
+
+## Automatic Updates
+
+Images are automatically rebuilt when:
+
+- New .NET patch versions are released (daily check)
+- Base image security updates are available (weekly rebuild)
+- A .NET version reaches EOL, it is automatically excluded
+
+Every build is scanned with Docker Scout and includes supply chain attestations (SBOM, SLSA provenance).
+
+## License
+
+MIT