From 46a5f40f232114412bbd5939f51b5eea6be04b04 Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Sat, 15 Aug 2026 12:10:45 +0000 Subject: [PATCH] Add Dockerfile + docker-compose for Coolify deployment Multi-stage build: node builds the SPA, dotnet publishes the API, the runtime image serves the SPA from wwwroot/ and exposes /health. Frontend Supabase URL + publishable key are baked in at build time; DB conn string and Supabase JWKS metadata come from Coolify env vars. VITE_API_URL empty means same-origin, so the browser hits /api/todos on the same host that serves the SPA. --- .dockerignore | 28 +++++++++++++++++++++++++++ Dockerfile | 43 +++++++++++++++++++++++++++++++++++++++++ backend/Program.cs | 5 +++++ docker-compose.yml | 25 ++++++++++++++++++++++++ frontend/src/lib/api.ts | 3 +-- 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..afd28c9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +**/bin/ +**/obj/ +**/node_modules/ +**/dist/ +**/.vite/ + +.git/ +.gitignore +.vs/ +.idea/ +.vscode/ + +*.user +*.swp + +**/appsettings.Development.json +**/appsettings.Local.json +.env +.env.local +frontend/.env +frontend/.env.local + +Dockerfile +docker-compose.yml +.dockerignore +README.md +docs/ +migrations/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6769136 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1.7 + +# 1. Frontend build +FROM node:20-alpine AS web +WORKDIR /src +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci --no-audit --no-fund +COPY frontend/ ./ + +# Public values baked into the client bundle at build time. +# API URL is empty in production (same-origin — the backend serves the SPA). +ARG VITE_SUPABASE_URL +ARG VITE_SUPABASE_PUBLISHABLE_KEY +ARG VITE_API_URL="" +ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL \ + VITE_SUPABASE_PUBLISHABLE_KEY=$VITE_SUPABASE_PUBLISHABLE_KEY \ + VITE_API_URL=$VITE_API_URL + +RUN npm run build + +# 2. Backend publish +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS api +WORKDIR /src +COPY backend/backend.csproj backend/ +RUN dotnet restore backend/backend.csproj +COPY backend/ backend/ +RUN dotnet publish backend/backend.csproj \ + -c Release -o /out --no-restore /p:UseAppHost=false + +# 3. Runtime +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime +WORKDIR /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* +COPY --from=api /out ./ +COPY --from=web /src/dist ./wwwroot +ENV ASPNETCORE_ENVIRONMENT=Production \ + ASPNETCORE_URLS=http://+:8080 +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD curl -fsS http://127.0.0.1:8080/health || exit 1 +ENTRYPOINT ["dotnet", "backend.dll"] diff --git a/backend/Program.cs b/backend/Program.cs index d1e56ec..e7abd33 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -49,9 +49,14 @@ if (app.Environment.IsDevelopment()) app.UseCors(DevCorsPolicy); } +app.UseDefaultFiles(); +app.UseStaticFiles(); + app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); +app.MapGet("/health", () => Results.Ok(new { status = "ok" })); +app.MapFallbackToFile("index.html"); app.Run(); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..23a7835 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + VITE_SUPABASE_URL: ${VITE_SUPABASE_URL} + VITE_SUPABASE_PUBLISHABLE_KEY: ${VITE_SUPABASE_PUBLISHABLE_KEY} + VITE_API_URL: "" + expose: + - "8080" + environment: + SERVICE_FQDN_APP_8080: / + ASPNETCORE_ENVIRONMENT: Production + ASPNETCORE_URLS: "http://+:8080" + ConnectionStrings__Postgres: ${DB_CONNECTION_STRING} + Supabase__MetadataAddress: ${SUPABASE_METADATA_ADDRESS} + Supabase__Issuer: ${SUPABASE_ISSUER} + Supabase__Audience: authenticated + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 20s diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9d82a8b..cdef8c5 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,7 +1,6 @@ import { supabase } from './supabase'; -const API_URL = import.meta.env.VITE_API_URL; -if (!API_URL) throw new Error('Missing VITE_API_URL'); +const API_URL = import.meta.env.VITE_API_URL ?? ''; export type Todo = { id: number;