Docker-Images bauen und veroeffentlichen / lint (push) Successful in 1m54s
Docker-Images bauen und veroeffentlichen / typecheck (push) Successful in 1m14s
Docker-Images bauen und veroeffentlichen / test (push) Successful in 1m35s
Docker-Images bauen und veroeffentlichen / build-and-push (push) Failing after 6m9s
Der Workflow baute bisher bei jedem Push auf main ungeprueft beide Images. Jetzt laufen lint (inkl. pnpm audit ab high), typecheck und test auf dem isolierten Runner (ci-isolated, ohne Docker-Socket und Clusternetz); die Tests bekommen Postgres 18.6 als Service ueber TEST_DATABASE_URL. build-and-push startet nur, wenn alle drei gruen sind. Muster und Runner-Pruefskript aus dem VyCept-Repo (docs/BETRIEB.md 11b). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
194 lines
7.5 KiB
YAML
194 lines
7.5 KiB
YAML
name: Docker-Images bauen und veroeffentlichen
|
|
|
|
# Baut aus einem Dockerfile ZWEI Images mit identischen Tags:
|
|
# <repo> Target "runner": der Next.js-Server
|
|
# <repo>-tools Target "tools": Migration (Kubernetes-Job), Worker (CronJob), admin:create
|
|
# Beide muessen immer mit demselben Tag ausgerollt werden (gleiches Datenbankschema).
|
|
#
|
|
# Ausloeser: Push auf main (":latest" + ":sha-<kurz>"), Versions-Tags "v1.2.3"
|
|
# (zusaetzlich ":1.2.3"), manuell ueber "Run workflow".
|
|
#
|
|
# Qualitaets-Gate: Images entstehen 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-
|
|
- 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
|
|
# embedded-postgres startet nicht als root; deshalb ein Datenbank-Service
|
|
# (gleiche Version wie im Cluster), genutzt ueber TEST_DATABASE_URL
|
|
# (tests/support/global-setup.ts).
|
|
services:
|
|
postgres:
|
|
image: postgres:18.6-alpine
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: artista_test
|
|
options: >-
|
|
--health-cmd "pg_isready -U test" --health-interval 5s --health-retries 10
|
|
env:
|
|
TEST_DATABASE_URL: postgresql://test:test@postgres:5432/artista_test
|
|
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 test
|
|
|
|
build-and-push:
|
|
needs: [lint, typecheck, test]
|
|
runs-on: docker-builder
|
|
env:
|
|
# Oeffentlicher Registry-Host (Pull durch Kubernetes, OCI-Labels).
|
|
REGISTRY: git.encry.de
|
|
# Push ueber den Cluster-internen Gitea-Service statt ueber git.encry.de:
|
|
# Blob-Uploads scheitern am Cloudflare-Proxy ("413 Payload Too Large").
|
|
# Gleiches Muster wie im Wabenhain-Repo; der Hostname ist nicht Teil des
|
|
# gespeicherten Images, gezogen wird weiterhin ueber git.encry.de.
|
|
REGISTRY_PUSH: gitea:80
|
|
steps:
|
|
- name: Code auschecken
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Image-Namen ermitteln
|
|
# Fester Produktname "artista-modelcamp" unabhaengig vom Repo-Namen;
|
|
# nur der Besitzer kommt aus Gitea, in Kleinbuchstaben (OCI-Vorgabe):
|
|
# "Nick.Rosenloecher" -> "nick.rosenloecher/artista-modelcamp".
|
|
id: image
|
|
run: echo "name=$(echo '${{ gitea.repository_owner }}' | tr '[:upper:]' '[:lower:]')/artista-modelcamp" >> "$GITEA_OUTPUT"
|
|
|
|
- name: Docker Buildx einrichten
|
|
uses: docker/setup-buildx-action@v3
|
|
with:
|
|
# Der interne Gitea-Service spricht HTTP ohne TLS.
|
|
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 darf keine Pakete schreiben: Personal Access Token mit
|
|
# Scope "package" als Repo-Secret REGISTRY_TOKEN hinterlegen
|
|
# (Repo-Einstellungen -> Actions -> Secrets).
|
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
- name: Metadaten Anwendung
|
|
id: meta-app
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY_PUSH }}/${{ steps.image.outputs.name }}
|
|
# Gleiche Liste wie bei den Werkzeugen (keine YAML-Anker: nicht
|
|
# jeder Gitea-Runner unterstuetzt sie).
|
|
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}}
|
|
|
|
# Der Build braucht Internet (next/font laedt die Schriften), aber weder
|
|
# Datenbank noch Secrets.
|
|
- name: Anwendung bauen und pushen
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
target: runner
|
|
push: true
|
|
tags: ${{ steps.meta-app.outputs.tags }}
|
|
labels: ${{ steps.meta-app.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
# 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
|
|
|
|
- name: Zusammenfassung
|
|
run: |
|
|
echo "Gebaut fuer ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}(-tools):"
|
|
echo "${{ steps.meta-app.outputs.tags }}"
|