From bbe792192d5bdadbae71f3a26bff38f2451014f0 Mon Sep 17 00:00:00 2001 From: tleininger Date: Wed, 23 Sep 2026 08:40:00 +0200 Subject: [PATCH] Split compose into base + dev overlay for a local two-container stack --- .dockerignore | 1 + .run/DevEnvironment.run.xml | 32 +++++++++++++++++++++++++++ Dockerfile | 10 +++++++-- compose.dev.yaml | 38 ++++++++++++++++++++++++++++++++ compose.yaml | 43 ++++++++++++++++++++++++++----------- 5 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 .run/DevEnvironment.run.xml create mode 100644 compose.dev.yaml diff --git a/.dockerignore b/.dockerignore index c6a6ce6..444d3cb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,3 +20,4 @@ docs/ Dockerfile .dockerignore compose.yaml +compose.dev.yaml diff --git a/.run/DevEnvironment.run.xml b/.run/DevEnvironment.run.xml new file mode 100644 index 0000000..f19183d --- /dev/null +++ b/.run/DevEnvironment.run.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/Dockerfile b/Dockerfile index a63399d..3221e31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,15 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src -# Copy only the csproj and restore first, so the NuGet restore layer stays -# cached as long as the dependencies do not change. +# Copy the solution-wide build files first (Central Package Management lives in +# Directory.Packages.props; without it a restore fails with NU1015). Then the +# csproj of every project in the web app's dependency tree. Restoring before +# copying the rest of the source keeps the NuGet layer cached as long as the +# dependencies do not change. +COPY Directory.Build.props Directory.Packages.props ./ COPY Elternbeirat.Web/Elternbeirat.Web.csproj Elternbeirat.Web/ +COPY Elternbeirat.PocketBase/Elternbeirat.PocketBase.csproj Elternbeirat.PocketBase/ +COPY Elternbeirat.Contracts/Elternbeirat.Contracts.csproj Elternbeirat.Contracts/ RUN dotnet restore Elternbeirat.Web/Elternbeirat.Web.csproj # Then the rest of the source. diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 0000000..860932b --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,38 @@ +# Dev overlay: layered on top of compose.yaml for local development. +# docker compose -f compose.yaml -f compose.dev.yaml up --build +# +# It adds host port mappings (so the stack is reachable from the browser), builds +# the web image from local source instead of pulling it from the registry, and +# swaps the Unraid pb_data bind mount for a throwaway named volume (the /mnt/user +# path only exists on the server). + +services: + eb-pocketbase: + ports: + - "8090:8090" # PocketBase admin UI at http://localhost:8090/_/ + environment: + # Local only: on a fresh pb_data the image auto-creates this superuser, so + # you skip the /_/ setup screen. Same credentials as the test fixture. This + # stays out of the prod base -- there the superuser is set up by hand / #9. + PB_ADMIN_EMAIL: test@example.com + PB_ADMIN_PASSWORD: test-password + volumes: + # Local: a named Docker volume instead of the Unraid /mnt/user bind mount + # from the base file (that path only exists on the server). The "!reset" + # tag clears the inherited bind mount so only this volume applies. + - !reset null + - pb_data_dev:/pb_data + + eb-web: + # Build from local source instead of pulling the registry image, so local code + # changes show up without a push+CI round-trip. image: "" clears the inherited + # tag from the base, which forces compose to use the build below. + image: "" + build: + context: . + dockerfile: Dockerfile + ports: + - "5000:8080" # web app at http://localhost:5000 + +volumes: + pb_data_dev: # throwaway local volume; remove with `docker volume rm` diff --git a/compose.yaml b/compose.yaml index e3ff3ef..6806670 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,24 +1,41 @@ -# TEST setup for the first run on Unraid (without NPM). +# Base compose: production-like. Both services, no host port mappings (NPM is the +# only path to the web app; the editors reach PocketBase through an NPM subdomain). +# Only PocketBase's data is a volume; everything else lives inside the images. # -# Pulls the finished image from the Gitea registry (the build happens on the -# development machine, see docs/deployment.md) instead of building from source -# on Unraid. +# For local development use the dev overlay on top, which adds host ports and builds +# the web image from source: +# docker compose -f compose.yaml -f compose.dev.yaml up --build +# (The Rider run config "DevEnvironment" does exactly this with one click.) # -# Deliberately differs from the production setup: -# - There is a port mapping (5000 outside -> 8080 inside) so the app is -# reachable in the local network at http://:5000. In production the -# mapping is dropped; there NPM is the only path to the container (use the -# external npm network instead, see docs/deployment.md). -# -# Once NPM is in place, this file is replaced by the production compose. +# Startup order: PocketBase comes up first; once it reports healthy the web app +# starts (depends_on -> service_healthy). services: + eb-pocketbase: + image: ghcr.io/muchobien/pocketbase:0.40.4 + container_name: eb-pocketbase + restart: unless-stopped + environment: + TZ: Europe/Berlin + volumes: + # The only state that must survive a restart or redeploy. + - /mnt/user/appdata/pocketbase/pb_data:/pb_data + healthcheck: + # The image ships this endpoint; the web app waits for it to pass. + test: ["CMD", "wget", "--spider", "-q", "http://localhost:8090/api/health"] + interval: 5s + timeout: 3s + retries: 10 + eb-web: image: gitea.anticarnist.de/tom/elternbeirat:latest container_name: eb-web restart: unless-stopped + depends_on: + eb-pocketbase: + condition: service_healthy environment: ASPNETCORE_URLS: http://+:8080 TZ: Europe/Berlin - ports: - - "5000:8080" # TEST access, remove in production + # The web app reads content from PocketBase over the compose network. + PocketBase__BaseUrl: http://eb-pocketbase:8090