Skip to content
Draft
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
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
**/%*
**/bin/
**/obj/
.git/
.vs/
.vscode/
7 changes: 3 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ name: Build app

on:
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
packages: read # Changed 'write' to 'read' for security as this doesn't push
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}

- name: Setup .NET
uses: actions/setup-dotnet@v4
Expand All @@ -27,4 +26,4 @@ jobs:
run: dotnet build -c Release --no-restore

- name: Test
run: dotnet test -c Release --no-build
run: dotnet test -c Release --no-build --verbosity normal
29 changes: 29 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Docker Build Check

on: [push, pull_request, workflow_dispatch]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Build Docker image
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: ./Dockerfile
push: false # We only want to test the build, not push it
tags: patchpanda:test
cache-from: type=gha
cache-to: type=gha,mode=max
Comment thread
coderabbitai[bot] marked this conversation as resolved.
43 changes: 22 additions & 21 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
@@ -1,48 +1,49 @@
name: Build and Publish Docker image to GHCR
name: Build and publish Docker image

on:
release:
types: [published]
types: [released]

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Required for security attestations
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Lowercase repo name
run: echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
uses: docker/setup-buildx-action@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}

- name: Extract release version
id: vars
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV

- name: Set lowercase repository name
id: repo-name
run: echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: ./Dockerfile
push: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
build-args: |
RELEASE_VERSION=${{ env.RELEASE_VERSION }}
RELEASE_VERSION=${{ github.event.release.tag_name }}
tags: |
ghcr.io/${{ env.REPO_LOWER }}:${{ env.RELEASE_VERSION }}
ghcr.io/${{ env.REPO_LOWER }}:latest
ghcr.io/${{ env.REPO_LC }}:latest
ghcr.io/${{ env.REPO_LC }}:${{ github.event.release.tag_name }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
68 changes: 32 additions & 36 deletions .github/workflows/docker-unstable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,73 +21,69 @@ jobs:
permissions:
contents: read
packages: write
id-token: write # Required for security attestations (v7)
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || github.ref }}

- name: Lowercase repo name
run: echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm
uses: docker/setup-buildx-action@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}

- name: Set lowercase repository name
id: repo-name
run: echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: ./Dockerfile
push: true
build-args: |
RELEASE_VERSION=unstable-${{ github.sha }}
tags: ghcr.io/${{ env.REPO_LOWER }}:unstable
tags: ghcr.io/${{ env.REPO_LC }}:unstable
provenance: true
sbom: true

test-multi-arch:
needs: build-and-push
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
strategy:
fail-fast: false # Run all architectures even if one fails
matrix:
arch: [amd64, arm64, arm]
arch: [amd64, arm64, arm/v7]
steps:
- name: Lowercase repo name
run: echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/${{ matrix.arch }}

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}

- name: Set lowercase repository name
id: repo-name
run: echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

- name: Pull Docker image for ${{ matrix.arch }}
run: |
docker pull --platform linux/${{ matrix.arch }} ghcr.io/${{ env.REPO_LOWER }}:unstable
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run test container for ${{ matrix.arch }}
- name: Test image on ${{ matrix.arch }}
run: |
docker run -p 8080:8080 -d --name patchpanda-test --platform linux/${{ matrix.arch }} ghcr.io/${{ env.REPO_LOWER }}:unstable
sleep 50
docker logs patchpanda-test
docker stop patchpanda-test
docker rm patchpanda-test
set -e
echo "Starting smoke test for ${{ matrix.arch }}..."
# We use a 3-minute timeout to allow slow arm/v7 emulation to boot fully.
# Exit code 124 means the timeout hit, which confirms the app stayed up.
timeout 180s docker run --rm --platform linux/${{ matrix.arch }} \
ghcr.io/${{ env.REPO_LC }}:unstable || [ $? -eq 124 ]
echo "Successfully verified image execution for ${{ matrix.arch }}"
Comment thread
xuara marked this conversation as resolved.
120 changes: 75 additions & 45 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,69 +1,99 @@
# Declare ARGs for build platform and target architecture for clarity
# PatchPanda.Web - Multi-Stage Docker Build (2026 Standards)
# Support: amd64, arm64, arm/v7
# Features: Non-root user, Security Patches, Docker-in-Docker CLI, OIDC Ready, Health Checks

ARG BUILDPLATFORM

# ============================================================================
# STAGE 1: Base Runtime (Hardened)
# ============================================================================
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base

WORKDIR /app
EXPOSE 8080

# Patch OS vulnerabilities and install basic dependencies
USER root
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends curl ca-certificates gnupg && \
rm -rf /var/lib/apt/lists/*
USER app

# ============================================================================
# STAGE 2: Build (Cross-Platform)
# ============================================================================
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build

ARG TARGETARCH
ARG TARGETVARIANT
WORKDIR /src

# Copy project file and restore specifically for the target architecture
COPY ["PatchPanda.Web/PatchPanda.Web.csproj", "PatchPanda.Web/"]

# Map Docker's TARGETARCH to the arch used in .NET RIDs and restore dependencies
RUN export DOTNET_ARCH=$(case ${TARGETARCH} in \
"amd64") echo "x64" ;; \
"arm64") echo "arm64" ;; \
"arm") echo "arm" ;; \
*) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;; \
esac) && \
dotnet restore "PatchPanda.Web/PatchPanda.Web.csproj" -r "linux-${DOTNET_ARCH}"
"amd64") echo "x64" ;; \
"arm64") echo "arm64" ;; \
"arm") [ "${TARGETVARIANT}" = "v7" ] && echo "arm" || { echo "ERROR: Unsupported ARM variant '${TARGETVARIANT}'"; exit 1; } ;; \
*) echo "ERROR: Unsupported architecture '${TARGETARCH}'"; exit 1 ;; \
esac) && \
dotnet restore "PatchPanda.Web/PatchPanda.Web.csproj" \
--runtime "linux-${DOTNET_ARCH}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Copy remaining source code
COPY . .

# Build and publish the application for the target runtime
# Publish the Release binary with strict ARMv7 validation
RUN export DOTNET_ARCH=$(case ${TARGETARCH} in \
"amd64") echo "x64" ;; \
"arm64") echo "arm64" ;; \
"arm") echo "arm" ;; \
esac) && \
dotnet publish "PatchPanda.Web/PatchPanda.Web.csproj" \
-c Release \
-o /app/publish \
-r "linux-${DOTNET_ARCH}" \
--no-restore \
--self-contained false
"amd64") echo "x64" ;; \
"arm64") echo "arm64" ;; \
"arm") [ "${TARGETVARIANT}" = "v7" ] && echo "arm" || { echo "ERROR: Unsupported ARM variant '${TARGETVARIANT}'"; exit 1; } ;; \
*) echo "ERROR: Unsupported architecture '${TARGETARCH}'"; exit 1 ;; \
esac) && \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
dotnet publish "PatchPanda.Web/PatchPanda.Web.csproj" \
--configuration Release \
--output /app/publish \
--runtime "linux-${DOTNET_ARCH}" \
--no-restore \
--self-contained false

# ============================================================================
# STAGE 3: Final Production Image (Ubuntu 26.04 LTS "Resolute")
# ============================================================================
FROM base AS final
# Install utilities needed for installing the Docker CLI
RUN apt-get update && \
apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release && \
\
# Add Docker's official GPG key
mkdir -m 0755 -p /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
\
# Set up the stable repository, explicitly using 'bookworm' (Debian 12)
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
\
# Install the Docker CLI only
apt-get update && \
apt-get install -y docker-ce-cli && \
\
# Clean up
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/publish .

# Set up environment and diagnostics
ARG RELEASE_VERSION
ARG ENABLE_DIAGNOSTICS=0
ENV APP_VERSION=$RELEASE_VERSION
LABEL version=$RELEASE_VERSION
ENV DOTNET_EnableDiagnostics=${ENABLE_DIAGNOSTICS}

# Install Docker CLI using Ubuntu 'resolute' repo
USER root
RUN mkdir -m 0755 -p /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
chmod a+r /etc/apt/keyrings/docker.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu resolute stable" > /etc/apt/sources.list.d/docker.list && \
apt-get update && \
apt-get install -y --no-install-recommends docker-ce-cli && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app
# Copy artifacts first (owned by root for security)
COPY --from=build /app/publish .

# Create data directory and fix permissions AFTER artifacts are copied
# Narrowed chown ensures binaries stay read-only while data is writable
RUN mkdir -p /app/data && \
chown -R app:app /app/data

HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1

LABEL version=$RELEASE_VERSION \
description="PatchPanda Web Application" \
maintainer="dkorecko"

USER app
ENTRYPOINT ["dotnet", "PatchPanda.Web.dll"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.