From a953a5fb1f3373f277a57eb62911ad425dab4a53 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Mon, 30 Mar 2026 22:29:56 +0200 Subject: [PATCH] Fix Dockerfile with full multi-stage build, frontend support, and non-root user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous Dockerfile was broken: only 7 of 34+ .csproj files were copied (restore failed), no Node.js for React/Vite frontend builds, and missing global.json/Tailwind CLI. This rewrites all three Docker files: Dockerfile: - 4-stage build: base (SDK+Node.js) → restore → build → runtime - All framework, module, and ServiceDefaults .csproj files in restore layer - Node.js 22 LTS for Vite/React frontend builds - Tailwind CSS CLI download for Linux (x64/arm64) - npm ci + npm run build for all frontend workspaces - Non-root appuser in runtime image - ErrorOnDuplicatePublishOutputFiles for module package.json conflicts docker-compose.yml: - Health check on API service (/health/live) - PostgreSQL 16 → 17 - Environment variable for passwords (POSTGRES_PASSWORD) - Added Database__Provider=PostgreSQL - Development environment for auto-migration - restart: unless-stopped on both services .dockerignore: - Exclude node_modules, bin/obj, tests, docs, cli, AppHost - Prevent huge Docker context transfers --- .dockerignore | 38 ++++++++++++++-- Dockerfile | 106 ++++++++++++++++++++++++++++++++++++++++----- docker-compose.yml | 19 ++++++-- 3 files changed, 145 insertions(+), 18 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6c62b842..9904339e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,40 @@ -bin/ -obj/ +# Build outputs +**/bin/ +**/obj/ +**/node_modules/ + +# Version control .git/ -tests/ +.gitignore + +# IDE / Editor .vs/ .vscode/ +.idea/ +*.suo +*.user + +# Tests (not needed for Host build) +tests/ +modules/*/tests/ + +# Development artifacts *.db +*.db-shm +*.db-wal .worktrees/ + +# Not needed in production image +SimpleModule.AppHost/ +cli/ +docs/ +website/ + +# CI/CD and tooling +.github/ +.claude/ + +# Docker (avoid context recursion) +Dockerfile* +docker-compose* +.dockerignore diff --git a/Dockerfile b/Dockerfile index 9d3272ff..d1cee992 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,111 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +# ============================================================================= +# Stage 1: Base build image with .NET SDK + Node.js +# ============================================================================= +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS base WORKDIR /src -COPY Directory.Build.props Directory.Packages.props ./ +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# ============================================================================= +# Stage 2: .NET restore (cached unless .csproj / props files change) +# ============================================================================= +FROM base AS restore +WORKDIR /src + +COPY global.json Directory.Build.props Directory.Packages.props .editorconfig ./ COPY *.slnx ./ + +# Framework projects COPY framework/SimpleModule.Core/*.csproj framework/SimpleModule.Core/ COPY framework/SimpleModule.Database/*.csproj framework/SimpleModule.Database/ COPY framework/SimpleModule.Generator/*.csproj framework/SimpleModule.Generator/ COPY framework/SimpleModule.Blazor/*.csproj framework/SimpleModule.Blazor/ +COPY framework/SimpleModule.Hosting/*.csproj framework/SimpleModule.Hosting/ +COPY framework/SimpleModule.DevTools/*.csproj framework/SimpleModule.DevTools/ +COPY framework/SimpleModule.Storage/*.csproj framework/SimpleModule.Storage/ +COPY framework/SimpleModule.Storage.Local/*.csproj framework/SimpleModule.Storage.Local/ + +# ServiceDefaults +COPY SimpleModule.ServiceDefaults/*.csproj SimpleModule.ServiceDefaults/ + +# Host COPY template/SimpleModule.Host/*.csproj template/SimpleModule.Host/ -COPY modules/Dashboard/src/Dashboard/*.csproj modules/Dashboard/src/Dashboard/ -COPY modules/Users/src/Users.Contracts/*.csproj modules/Users/src/Users.Contracts/ -COPY modules/Users/src/Users/*.csproj modules/Users/src/Users/ -COPY modules/Products/src/Products.Contracts/*.csproj modules/Products/src/Products.Contracts/ -COPY modules/Products/src/Products/*.csproj modules/Products/src/Products/ -COPY modules/Orders/src/Orders.Contracts/*.csproj modules/Orders/src/Orders.Contracts/ -COPY modules/Orders/src/Orders/*.csproj modules/Orders/src/Orders/ + +# Modules (Contracts + Implementation) +COPY modules/Dashboard/src/SimpleModule.Dashboard.Contracts/*.csproj modules/Dashboard/src/SimpleModule.Dashboard.Contracts/ +COPY modules/Dashboard/src/SimpleModule.Dashboard/*.csproj modules/Dashboard/src/SimpleModule.Dashboard/ +COPY modules/Users/src/SimpleModule.Users.Contracts/*.csproj modules/Users/src/SimpleModule.Users.Contracts/ +COPY modules/Users/src/SimpleModule.Users/*.csproj modules/Users/src/SimpleModule.Users/ +COPY modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict.Contracts/ +COPY modules/OpenIddict/src/SimpleModule.OpenIddict/*.csproj modules/OpenIddict/src/SimpleModule.OpenIddict/ +COPY modules/Permissions/src/SimpleModule.Permissions.Contracts/*.csproj modules/Permissions/src/SimpleModule.Permissions.Contracts/ +COPY modules/Permissions/src/SimpleModule.Permissions/*.csproj modules/Permissions/src/SimpleModule.Permissions/ +COPY modules/Products/src/SimpleModule.Products.Contracts/*.csproj modules/Products/src/SimpleModule.Products.Contracts/ +COPY modules/Products/src/SimpleModule.Products/*.csproj modules/Products/src/SimpleModule.Products/ +COPY modules/Orders/src/SimpleModule.Orders.Contracts/*.csproj modules/Orders/src/SimpleModule.Orders.Contracts/ +COPY modules/Orders/src/SimpleModule.Orders/*.csproj modules/Orders/src/SimpleModule.Orders/ +COPY modules/Admin/src/SimpleModule.Admin.Contracts/*.csproj modules/Admin/src/SimpleModule.Admin.Contracts/ +COPY modules/Admin/src/SimpleModule.Admin/*.csproj modules/Admin/src/SimpleModule.Admin/ +COPY modules/PageBuilder/src/SimpleModule.PageBuilder.Contracts/*.csproj modules/PageBuilder/src/SimpleModule.PageBuilder.Contracts/ +COPY modules/PageBuilder/src/SimpleModule.PageBuilder/*.csproj modules/PageBuilder/src/SimpleModule.PageBuilder/ +COPY modules/Settings/src/SimpleModule.Settings.Contracts/*.csproj modules/Settings/src/SimpleModule.Settings.Contracts/ +COPY modules/Settings/src/SimpleModule.Settings/*.csproj modules/Settings/src/SimpleModule.Settings/ +COPY modules/AuditLogs/src/SimpleModule.AuditLogs.Contracts/*.csproj modules/AuditLogs/src/SimpleModule.AuditLogs.Contracts/ +COPY modules/AuditLogs/src/SimpleModule.AuditLogs/*.csproj modules/AuditLogs/src/SimpleModule.AuditLogs/ +COPY modules/Marketplace/src/SimpleModule.Marketplace.Contracts/*.csproj modules/Marketplace/src/SimpleModule.Marketplace.Contracts/ +COPY modules/Marketplace/src/SimpleModule.Marketplace/*.csproj modules/Marketplace/src/SimpleModule.Marketplace/ +COPY modules/FileStorage/src/SimpleModule.FileStorage.Contracts/*.csproj modules/FileStorage/src/SimpleModule.FileStorage.Contracts/ +COPY modules/FileStorage/src/SimpleModule.FileStorage/*.csproj modules/FileStorage/src/SimpleModule.FileStorage/ +COPY modules/FeatureFlags/src/SimpleModule.FeatureFlags.Contracts/*.csproj modules/FeatureFlags/src/SimpleModule.FeatureFlags.Contracts/ +COPY modules/FeatureFlags/src/SimpleModule.FeatureFlags/*.csproj modules/FeatureFlags/src/SimpleModule.FeatureFlags/ + RUN dotnet restore template/SimpleModule.Host/SimpleModule.Host.csproj +# ============================================================================= +# Stage 3: Frontend build + .NET publish +# ============================================================================= +FROM restore AS build +WORKDIR /src + COPY . . -RUN dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj -c Release -o /app/publish +# Download Tailwind CSS CLI for Linux +RUN ARCH=$(uname -m) && \ + case "$ARCH" in x86_64) ARCH="x64" ;; aarch64|arm64) ARCH="arm64" ;; esac && \ + curl -sL "https://github.com/tailwindlabs/tailwindcss/releases/download/v4.1.3/tailwindcss-linux-${ARCH}" -o tools/tailwindcss && \ + chmod +x tools/tailwindcss + +# Install npm dependencies and build all frontend workspaces +RUN npm ci +RUN npm run build + +# Publish the .NET application (MSBuild targets detect existing frontend +# outputs and only run TailwindBuild which needs Blazor SSR content) +RUN dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj \ + -c Release \ + -o /app/publish \ + --no-restore \ + -p:ErrorOnDuplicatePublishOutputFiles=false + +# ============================================================================= +# Stage 4: Runtime (slim image, non-root user) +# ============================================================================= FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime WORKDIR /app -COPY --from=build /app/publish . + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && apt-get clean && rm -rf /var/lib/apt/lists/* \ + && groupadd --system --gid 1001 appgroup \ + && useradd --system --uid 1001 --gid appgroup --create-home appuser + +COPY --from=build --chown=appuser:appgroup /app/publish . + +USER appuser EXPOSE 8080 + ENTRYPOINT ["dotnet", "SimpleModule.Host.dll"] diff --git a/docker-compose.yml b/docker-compose.yml index 818d4eb3..8bb6df1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,17 +4,27 @@ services: ports: - "8080:8080" environment: - - ASPNETCORE_ENVIRONMENT=Production - - Database__DefaultConnection=Host=postgres;Database=simplemodule;Username=simplemodule;Password=simplemodule + # Development enables auto-migration. For production, apply migrations + # externally and set to Production. + ASPNETCORE_ENVIRONMENT: Development + Database__DefaultConnection: "Host=postgres;Port=5432;Database=simplemodule;Username=simplemodule;Password=${POSTGRES_PASSWORD:-simplemodule}" + Database__Provider: PostgreSQL depends_on: postgres: condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8080/health/live || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + restart: unless-stopped postgres: - image: postgres:16 + image: postgres:17 environment: POSTGRES_USER: simplemodule - POSTGRES_PASSWORD: simplemodule + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-simplemodule} POSTGRES_DB: simplemodule ports: - "5432:5432" @@ -25,6 +35,7 @@ services: interval: 10s timeout: 5s retries: 5 + restart: unless-stopped volumes: pgdata: