name: Docker Image bauen und veroeffentlichen # Laeuft bei jedem Push auf main (rollierendes ":latest" + ":") # und bei Versions-Tags wie "v1.2.3" (zusaetzlich als eigener Tag). Manuell # ueber "Run workflow" in der Gitea-Weboberflaeche ebenfalls startbar. # # Qualitaets-Gate: Ein Image entsteht nur, wenn lint (inkl. audit), typecheck # und test gruen sind. Die Pruefungen laufen auf "ci-isolated" (Jobs ohne # Docker-Socket und ohne Clusternetz, Runner-Doku im VyCept-Repo, # docs/BETRIEB.md 11b); nur der Image-Build braucht "docker-builder". on: push: branches: [main] tags: ["v*"] workflow_dispatch: # Ein neuer Push bricht den ueberholten Lauf ab, statt ihn zu Ende zu rechnen. concurrency: group: docker-build-${{ gitea.ref }} cancel-in-progress: true env: NEXT_TELEMETRY_DISABLED: "1" jobs: lint: runs-on: ci-isolated timeout-minutes: 10 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Runner-Isolation pruefen run: bash scripts/ci-runner-isolation.sh - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: { node-version: "22" } - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/pnpm ~/.cache/pnpm-store key: pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: pnpm- - run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile - run: pnpm lint # Bekannte Luecken in Laufzeit-Abhaengigkeiten (ab "high") brechen den Lauf ab. - run: pnpm audit --prod --audit-level high typecheck: runs-on: ci-isolated timeout-minutes: 10 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Runner-Isolation pruefen run: bash scripts/ci-runner-isolation.sh - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: { node-version: "22" } - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/pnpm ~/.cache/pnpm-store key: pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: pnpm- # postinstall erzeugt beide Prisma-Clients (Postgres + MySQL), die tsc braucht. - run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile - run: pnpm typecheck test: runs-on: ci-isolated timeout-minutes: 15 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Runner-Isolation pruefen run: bash scripts/ci-runner-isolation.sh - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: { node-version: "22" } - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/pnpm ~/.cache/pnpm-store key: pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: pnpm- - run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile # Unit- und Komponententests (jsdom), ohne Datenbank. - run: pnpm test build-and-push: needs: [lint, typecheck, test] runs-on: docker-builder env: # Fest hinterlegt statt ${{ gitea.server_url }}: das hat in manchen # Gitea-Versionen einen Bug (leerer Wert im Runner, siehe # go-gitea/gitea#23276) und liefert selbst wenn es funktioniert eine # volle URL MIT "https://"-Schema — docker login/Image-Tags brauchen # aber den reinen Hostnamen ohne Schema. Bei einem Umzug auf eine # andere Gitea-Instanz hier anpassen. Nur noch fuer Doku/OCI-Labels # relevant — der eigentliche Push laeuft ueber REGISTRY_PUSH (s.u.), # Kubernetes zieht die Images weiterhin ueber diesen Hostnamen. REGISTRY: git.encry.de # Gitea-Registry-Push laeuft ueber den Cluster-internen Service-Namen # statt git.encry.de: Docker-Registry-Blob-Uploads (PUT) vertragen sich # nicht mit dem Cloudflare-Proxy vor git.encry.de — "413 Payload Too # Large" auch bei kleinen Layern, unabhaengig von der eigentlichen # Groesse. Der Runner-Pod (gitea-runner, Namespace "gitea") erreicht # den Gitea-Service direkt ueber Kubernetes-DNS ("gitea:80" — siehe # dessen eigene GITEA_INSTANCE_URL-Env, die denselben Hostnamen nutzt), # ohne ueber Cloudflare zu hairpinnen. Push und Pull landen im selben # Registry-Pfad (Owner/Repo:Tag) — der Hostname ist nur die Verbindung, # nicht Teil des gespeicherten Images, daher bleibt der Pull in den # k8s-Manifesten unveraendert ueber git.encry.de. REGISTRY_PUSH: gitea:80 steps: - name: Code auschecken uses: actions/checkout@v4 - name: Image-Namen in Kleinbuchstaben ermitteln # OCI-Registries erlauben nur Kleinbuchstaben im Repository-Pfad; # ${{ gitea.repository }} ("Owner/Repo") behaelt die Gross-/ # Kleinschreibung des Gitea-Repos bei. id: image run: echo "name=$(echo '${{ gitea.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITEA_OUTPUT" - name: Docker Buildx einrichten uses: docker/setup-buildx-action@v3 with: # REGISTRY_PUSH ist Klartext-HTTP (Gitea-Service intern ohne TLS) # statt HTTPS — BuildKit lehnt das ohne diese Freigabe ab. config-inline: | [registry."gitea:80"] http = true - name: Bei der Gitea-Container-Registry anmelden uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY_PUSH }} username: ${{ gitea.actor }} # GITEA_TOKEN hat aktuell KEIN Package-Write-Recht (Gitea- # Einschraenkung, Stand heute) — deshalb ein separates Personal # Access Token als Repo-/Org-Secret REGISTRY_TOKEN anlegen # (Scope: "package", Gitea-Einstellungen -> Anwendungen -> # Access Tokens) und unter Repo-Einstellungen -> Actions -> # Secrets als REGISTRY_TOKEN hinterlegen. password: ${{ secrets.REGISTRY_TOKEN }} - name: Metadaten (Tags/Labels) ermitteln id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY_PUSH }}/${{ steps.image.outputs.name }} tags: | type=raw,value=latest,enable={{is_default_branch}} type=sha,format=short type=semver,pattern={{version}} - name: Metadaten Werkzeuge id: meta-tools uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY_PUSH }}/${{ steps.image.outputs.name }}-tools tags: | type=raw,value=latest,enable={{is_default_branch}} type=sha,format=short type=semver,pattern={{version}} # Ein einzelnes Image traegt beide Prisma-Client-Varianten (Postgres + # MySQL, waehlt zur Laufzeit per DATABASE_PROVIDER, siehe # src/lib/prisma.ts) UND die Migrations-/Seed-Tools (siehe Dockerfile- # Kommentar bei "AS migrator") — die App migriert/seedet sich beim # Start selbst (idempotent), kein separates Migrations-Image/Job mehr. - name: Image bauen und pushen uses: docker/build-push-action@v6 with: context: . target: runner push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # Werkzeug-Image fuer die CronJobs (Dockerfile "AS tools"); nutzt die # gemeinsamen Build-Stufen aus dem Cache des ersten Builds. - name: Werkzeuge bauen und pushen uses: docker/build-push-action@v6 with: context: . target: tools push: true tags: ${{ steps.meta-tools.outputs.tags }} labels: ${{ steps.meta-tools.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max