diff --git a/DOCKERHUB.md b/DOCKERHUB.md
index 21e0467..aa223f7 100644
--- a/DOCKERHUB.md
+++ b/DOCKERHUB.md
@@ -46,9 +46,9 @@ Minimal .NET Docker images for production and CI/CD. Built on [Wolfi](https://wo
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
-WORKDIR /source
-COPY . .
-WORKDIR /source/MyApp.Api
+WORKDIR /app/source
+COPY --chown=nonroot . .
+WORKDIR /app/source/MyApp.Api
ARG TARGETARCH
RUN case "$TARGETARCH" in \
@@ -56,11 +56,11 @@ RUN case "$TARGETARCH" in \
arm64) ARCH=arm64 ;; \
*) ARCH=$TARGETARCH ;; \
esac && \
- dotnet publish -a $ARCH --self-contained false -o /app
+ dotnet publish -a $ARCH --self-contained false -o /app/publish
FROM svrnty/dotnet:runtime-invariant-10 AS final
WORKDIR /app
-COPY --from=build /app .
+COPY --from=build /app/publish .
USER 65532
EXPOSE 8080
ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
@@ -72,8 +72,8 @@ ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-lts AS build
-WORKDIR /source
-COPY . .
+WORKDIR /app/source
+COPY --chown=nonroot . .
ARG TARGETARCH
RUN case "$TARGETARCH" in \
@@ -81,11 +81,11 @@ RUN case "$TARGETARCH" in \
arm64) ARCH=arm64 ;; \
*) ARCH=$TARGETARCH ;; \
esac && \
- dotnet publish MyWorker -a $ARCH --self-contained false -o /app
+ dotnet publish MyWorker -a $ARCH --self-contained false -o /app/publish
FROM svrnty/dotnet:runtime-invariant-lts AS final
WORKDIR /app
-COPY --from=build /app .
+COPY --from=build /app/publish .
USER 65532
ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyWorker.dll"]
```
diff --git a/README.md b/README.md
index bf874dd..c1a16db 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,11 @@ Minimal .NET Docker images for production and CI/CD. Built on [Wolfi](https://wo
|---------|----------|-------|-------------------|------|
| **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 |
+| **sdk** | .NET SDK + bash, git, curl | Yes (bash) | Yes | 65532 (nonroot) |
- **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.
+- **sdk** - Everything needed to build .NET apps. Uses `DOTNET_CLI_HOME=/home/nonroot` for NuGet cache — no root required. Use `COPY --chown=nonroot` to make source files writable.
## Why Wolfi?
@@ -51,9 +51,9 @@ Minimal .NET Docker images for production and CI/CD. Built on [Wolfi](https://wo
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
-WORKDIR /source
-COPY . .
-WORKDIR /source/MyApp.Api
+WORKDIR /app/source
+COPY --chown=nonroot . .
+WORKDIR /app/source/MyApp.Api
ARG TARGETARCH
RUN case "$TARGETARCH" in \
@@ -61,11 +61,11 @@ RUN case "$TARGETARCH" in \
arm64) ARCH=arm64 ;; \
*) ARCH=$TARGETARCH ;; \
esac && \
- dotnet publish -a $ARCH --self-contained false -o /app
+ dotnet publish -a $ARCH --self-contained false -o /app/publish
FROM svrnty/dotnet:runtime-invariant-10 AS final
WORKDIR /app
-COPY --from=build /app .
+COPY --from=build /app/publish .
USER 65532
EXPOSE 8080
ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
@@ -77,8 +77,8 @@ ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.Api.dll"]
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-lts AS build
-WORKDIR /source
-COPY . .
+WORKDIR /app/source
+COPY --chown=nonroot . .
ARG TARGETARCH
RUN case "$TARGETARCH" in \
@@ -86,11 +86,11 @@ RUN case "$TARGETARCH" in \
arm64) ARCH=arm64 ;; \
*) ARCH=$TARGETARCH ;; \
esac && \
- dotnet publish MyApp.sln -a $ARCH --self-contained false -o /app
+ dotnet publish MyApp.sln -a $ARCH --self-contained false -o /app/publish
FROM svrnty/dotnet:runtime-lts AS final
WORKDIR /app
-COPY --from=build /app .
+COPY --from=build /app/publish .
USER 65532
EXPOSE 8080
ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.dll"]
@@ -102,8 +102,8 @@ ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyApp.dll"]
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM svrnty/dotnet:sdk-10 AS build
-WORKDIR /source
-COPY . .
+WORKDIR /app/source
+COPY --chown=nonroot . .
ARG TARGETARCH
RUN case "$TARGETARCH" in \
@@ -111,11 +111,11 @@ RUN case "$TARGETARCH" in \
arm64) ARCH=arm64 ;; \
*) ARCH=$TARGETARCH ;; \
esac && \
- dotnet publish MyWorker -a $ARCH --self-contained false -o /app
+ dotnet publish MyWorker -a $ARCH --self-contained false -o /app/publish
FROM svrnty/dotnet:runtime-invariant-10 AS final
WORKDIR /app
-COPY --from=build /app .
+COPY --from=build /app/publish .
USER 65532
ENTRYPOINT ["/usr/share/dotnet/dotnet", "MyWorker.dll"]
```
diff --git a/apko/sdk.yaml b/apko/sdk.yaml
index 612b227..8b804f9 100644
--- a/apko/sdk.yaml
+++ b/apko/sdk.yaml
@@ -25,7 +25,19 @@ accounts:
- username: nonroot
uid: 65532
gid: 65532
- run-as: 0
+ run-as: 65532
+
+paths:
+ - path: /home/nonroot
+ type: directory
+ uid: 65532
+ gid: 65532
+ permissions: 0o755
+ - path: /app
+ type: directory
+ uid: 65532
+ gid: 65532
+ permissions: 0o755
archs:
- x86_64
diff --git a/dockerfiles/sdk.Dockerfile b/dockerfiles/sdk.Dockerfile
index b1a5fd6..75b7790 100644
--- a/dockerfiles/sdk.Dockerfile
+++ b/dockerfiles/sdk.Dockerfile
@@ -6,5 +6,7 @@ ENV DOTNET_ROOT=/usr/share/dotnet
ENV PATH="/usr/share/dotnet:${PATH}"
ENV DOTNET_RUNNING_IN_CONTAINER=true
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
+ENV HOME=/home/nonroot
+ENV DOTNET_CLI_HOME=/home/nonroot
WORKDIR /app
-USER 0
+USER 65532