Mirror of https://forge.levinkeller.de/levin/dorfarchiv.git
Migrate from Coolify to K3S/Flux GitOps (Variant B)
- Dockerfile: 2-stage node:22-alpine build -> nginx:alpine serving build/ - nginx.conf: listen 8080, relative redirects, try_files, /assets/ caching, 404 - .github/workflows/deploy.yml: native amd64+arm64 runners, per-arch push, imagetools merge, kustomize image bump to main (no QEMU) - deploy/overlays/production: namespace/deployment/service/ingress/kustomization for host archiv.xn--rssing-wxa.de (public, no oauth2-proxy), TLS via cert-manager LFS not required at build time: *.zip/*.MP4 are linked as absolute external URLs to git-lfs.archiv.xn--rssing-wxa.de; relatively-referenced PDFs/DWGs/JPEG are committed as normal git blobs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Showing 8 changed files with 264 additions and 0 deletions.
| @@ -0,0 +1,113 @@ | ||
| 1 | added: name: Build & Deploy | |
| 2 | added: | |
| 3 | added: # Variant B: Multi-Arch-Image auf NATIVEN Runnern bauen (amd64 + arm64), | |
| 4 | added: # per-arch pushen, mit `docker buildx imagetools create` zu einem Manifest | |
| 5 | added: # zusammenführen — niemals QEMU. Danach den Image-Tag im Production-Overlay | |
| 6 | added: # bumpen und nach main committen ([skip ci]-Loop-Guard). Flux deployt. | |
| 7 | added: | |
| 8 | added: on: | |
| 9 | added: push: | |
| 10 | added: branches: [main] | |
| 11 | added: paths-ignore: | |
| 12 | added: - "deploy/**" | |
| 13 | added: - "**/*.md" | |
| 14 | added: workflow_dispatch: | |
| 15 | added: | |
| 16 | added: concurrency: | |
| 17 | added: group: deploy-${{ github.ref }} | |
| 18 | added: cancel-in-progress: false | |
| 19 | added: | |
| 20 | added: env: | |
| 21 | added: IMAGE: ghcr.io/dorfpflege-rossing/dorfarchiv | |
| 22 | added: | |
| 23 | added: jobs: | |
| 24 | added: # 1) Pro Architektur ein natives Image bauen und als per-arch Tag pushen. | |
| 25 | added: build: | |
| 26 | added: strategy: | |
| 27 | added: fail-fast: true | |
| 28 | added: matrix: | |
| 29 | added: include: | |
| 30 | added: - arch: amd64 | |
| 31 | added: runner: ubuntu-latest | |
| 32 | added: - arch: arm64 | |
| 33 | added: runner: ubuntu-24.04-arm | |
| 34 | added: runs-on: ${{ matrix.runner }} | |
| 35 | added: permissions: | |
| 36 | added: contents: read | |
| 37 | added: packages: write | |
| 38 | added: steps: | |
| 39 | added: - uses: actions/checkout@v4 | |
| 40 | added: | |
| 41 | added: - name: Set up Docker Buildx | |
| 42 | added: uses: docker/setup-buildx-action@v3 | |
| 43 | added: | |
| 44 | added: - name: Log in to GHCR | |
| 45 | added: uses: docker/login-action@v3 | |
| 46 | added: with: | |
| 47 | added: registry: ghcr.io | |
| 48 | added: username: ${{ github.actor }} | |
| 49 | added: password: ${{ secrets.GITHUB_TOKEN }} | |
| 50 | added: | |
| 51 | added: - name: Build & push (${{ matrix.arch }}) | |
| 52 | added: uses: docker/build-push-action@v6 | |
| 53 | added: with: | |
| 54 | added: context: . | |
| 55 | added: platforms: linux/${{ matrix.arch }} | |
| 56 | added: push: true | |
| 57 | added: provenance: false | |
| 58 | added: tags: ${{ env.IMAGE }}:${{ github.sha }}-${{ matrix.arch }} | |
| 59 | added: cache-from: type=gha,scope=${{ matrix.arch }} | |
| 60 | added: cache-to: type=gha,scope=${{ matrix.arch }},mode=max | |
| 61 | added: | |
| 62 | added: # 2) Per-arch Tags zu einem Multi-Arch-Manifest zusammenführen (kein QEMU). | |
| 63 | added: merge: | |
| 64 | added: needs: build | |
| 65 | added: runs-on: ubuntu-latest | |
| 66 | added: permissions: | |
| 67 | added: contents: read | |
| 68 | added: packages: write | |
| 69 | added: steps: | |
| 70 | added: - name: Log in to GHCR | |
| 71 | added: uses: docker/login-action@v3 | |
| 72 | added: with: | |
| 73 | added: registry: ghcr.io | |
| 74 | added: username: ${{ github.actor }} | |
| 75 | added: password: ${{ secrets.GITHUB_TOKEN }} | |
| 76 | added: | |
| 77 | added: - name: Create multi-arch manifest | |
| 78 | added: run: | | |
| 79 | added: docker buildx imagetools create \ | |
| 80 | added: --tag "${IMAGE}:${{ github.sha }}" \ | |
| 81 | added: --tag "${IMAGE}:latest" \ | |
| 82 | added: "${IMAGE}:${{ github.sha }}-amd64" \ | |
| 83 | added: "${IMAGE}:${{ github.sha }}-arm64" | |
| 84 | added: | |
| 85 | added: # 3) Image-Tag im Production-Overlay bumpen und nach main committen. | |
| 86 | added: bump: | |
| 87 | added: needs: merge | |
| 88 | added: runs-on: ubuntu-latest | |
| 89 | added: permissions: | |
| 90 | added: contents: write | |
| 91 | added: steps: | |
| 92 | added: - uses: actions/checkout@v4 | |
| 93 | added: with: | |
| 94 | added: ref: main | |
| 95 | added: | |
| 96 | added: - name: Install kustomize | |
| 97 | added: uses: imranismail/setup-kustomize@v2 | |
| 98 | added: | |
| 99 | added: - name: Bump image tag in production overlay | |
| 100 | added: working-directory: deploy/overlays/production | |
| 101 | added: run: kustomize edit set image "${IMAGE}=${IMAGE}:${{ github.sha }}" | |
| 102 | added: | |
| 103 | added: - name: Commit & push bump | |
| 104 | added: run: | | |
| 105 | added: git config user.name "github-actions[bot]" | |
| 106 | added: git config user.email "github-actions[bot]@users.noreply.github.com" | |
| 107 | added: if git diff --quiet; then | |
| 108 | added: echo "Kein Image-Bump nötig." | |
| 109 | added: exit 0 | |
| 110 | added: fi | |
| 111 | added: git add deploy/overlays/production/kustomization.yaml | |
| 112 | added: git commit -m "ci: deploy dorfarchiv ${{ github.sha }} [skip ci]" | |
| 113 | added: git push | |
| @@ -0,0 +1,18 @@ | ||
| 1 | added: # --- Stage 1: Build the static Docusaurus site --- | |
| 2 | added: FROM node:22-alpine AS build | |
| 3 | added: WORKDIR /app | |
| 4 | added: | |
| 5 | added: # Abhängigkeiten zuerst (Layer-Cache) | |
| 6 | added: COPY package.json package-lock.json* ./ | |
| 7 | added: RUN npm ci | |
| 8 | added: | |
| 9 | added: # Quellcode kopieren und statisch bauen | |
| 10 | added: COPY . . | |
| 11 | added: RUN npm run build | |
| 12 | added: | |
| 13 | added: # --- Stage 2: Serve build/ via nginx --- | |
| 14 | added: FROM nginx:alpine AS runtime | |
| 15 | added: COPY nginx.conf /etc/nginx/conf.d/default.conf | |
| 16 | added: COPY --from=build /app/build /usr/share/nginx/html | |
| 17 | added: EXPOSE 8080 | |
| 18 | added: CMD ["nginx", "-g", "daemon off;"] | |
| @@ -0,0 +1,43 @@ | ||
| 1 | added: apiVersion: apps/v1 | |
| 2 | added: kind: Deployment | |
| 3 | added: metadata: | |
| 4 | added: name: dorfarchiv | |
| 5 | added: namespace: dorfarchiv | |
| 6 | added: labels: | |
| 7 | added: app: dorfarchiv | |
| 8 | added: spec: | |
| 9 | added: replicas: 1 | |
| 10 | added: selector: | |
| 11 | added: matchLabels: | |
| 12 | added: app: dorfarchiv | |
| 13 | added: template: | |
| 14 | added: metadata: | |
| 15 | added: labels: | |
| 16 | added: app: dorfarchiv | |
| 17 | added: spec: | |
| 18 | added: containers: | |
| 19 | added: - name: web | |
| 20 | added: # Image-Referenz wird von kustomize (images:) verwaltet — bare ref. | |
| 21 | added: # Öffentliche Seite: nginx lauscht direkt auf 8080 (kein oauth2-proxy). | |
| 22 | added: image: ghcr.io/dorfpflege-rossing/dorfarchiv | |
| 23 | added: ports: | |
| 24 | added: - containerPort: 8080 | |
| 25 | added: name: http | |
| 26 | added: readinessProbe: | |
| 27 | added: httpGet: | |
| 28 | added: path: / | |
| 29 | added: port: 8080 | |
| 30 | added: initialDelaySeconds: 3 | |
| 31 | added: periodSeconds: 10 | |
| 32 | added: livenessProbe: | |
| 33 | added: httpGet: | |
| 34 | added: path: / | |
| 35 | added: port: 8080 | |
| 36 | added: initialDelaySeconds: 5 | |
| 37 | added: periodSeconds: 20 | |
| 38 | added: resources: | |
| 39 | added: requests: | |
| 40 | added: cpu: 10m | |
| 41 | added: memory: 24Mi | |
| 42 | added: limits: | |
| 43 | added: memory: 64Mi | |
| @@ -0,0 +1,25 @@ | ||
| 1 | added: apiVersion: networking.k8s.io/v1 | |
| 2 | added: kind: Ingress | |
| 3 | added: metadata: | |
| 4 | added: name: dorfarchiv | |
| 5 | added: namespace: dorfarchiv | |
| 6 | added: annotations: | |
| 7 | added: cert-manager.io/cluster-issuer: letsencrypt | |
| 8 | added: # Öffentliche Seite — kein Zugriffsschutz. Ingress zeigt direkt auf nginx (8080). | |
| 9 | added: spec: | |
| 10 | added: ingressClassName: traefik | |
| 11 | added: tls: | |
| 12 | added: - hosts: | |
| 13 | added: - archiv.xn--rssing-wxa.de | |
| 14 | added: secretName: dorfarchiv-tls | |
| 15 | added: rules: | |
| 16 | added: - host: archiv.xn--rssing-wxa.de | |
| 17 | added: http: | |
| 18 | added: paths: | |
| 19 | added: - path: / | |
| 20 | added: pathType: Prefix | |
| 21 | added: backend: | |
| 22 | added: service: | |
| 23 | added: name: dorfarchiv | |
| 24 | added: port: | |
| 25 | added: number: 8080 | |
| @@ -0,0 +1,16 @@ | ||
| 1 | added: apiVersion: kustomize.config.k8s.io/v1beta1 | |
| 2 | added: kind: Kustomization | |
| 3 | added: | |
| 4 | added: resources: | |
| 5 | added: - namespace.yaml | |
| 6 | added: - deployment.yaml | |
| 7 | added: - service.yaml | |
| 8 | added: - ingress.yaml | |
| 9 | added: | |
| 10 | added: # Der konkrete Image-Tag wird von der CI via | |
| 11 | added: # kustomize edit set image ghcr.io/dorfpflege-rossing/dorfarchiv=...:<git-sha> | |
| 12 | added: # auf den gebauten Multi-Arch-Tag gesetzt und nach main committet. | |
| 13 | added: images: | |
| 14 | added: - name: ghcr.io/dorfpflege-rossing/dorfarchiv | |
| 15 | added: newName: ghcr.io/dorfpflege-rossing/dorfarchiv | |
| 16 | added: newTag: latest | |
| @@ -0,0 +1,4 @@ | ||
| 1 | added: apiVersion: v1 | |
| 2 | added: kind: Namespace | |
| 3 | added: metadata: | |
| 4 | added: name: dorfarchiv | |
| @@ -0,0 +1,14 @@ | ||
| 1 | added: apiVersion: v1 | |
| 2 | added: kind: Service | |
| 3 | added: metadata: | |
| 4 | added: name: dorfarchiv | |
| 5 | added: namespace: dorfarchiv | |
| 6 | added: labels: | |
| 7 | added: app: dorfarchiv | |
| 8 | added: spec: | |
| 9 | added: selector: | |
| 10 | added: app: dorfarchiv | |
| 11 | added: ports: | |
| 12 | added: - name: http | |
| 13 | added: port: 8080 | |
| 14 | added: targetPort: 8080 | |
| @@ -0,0 +1,31 @@ | ||
| 1 | added: server { | |
| 2 | added: listen 8080; | |
| 3 | added: listen [::]:8080; | |
| 4 | added: server_name _; | |
| 5 | added: | |
| 6 | added: root /usr/share/nginx/html; | |
| 7 | added: index index.html; | |
| 8 | added: | |
| 9 | added: # Hinter Traefik: relative Redirects, damit Verzeichnis-Slash-Redirects nicht | |
| 10 | added: # den internen Port 8080 / http durchsickern lassen. | |
| 11 | added: absolute_redirect off; | |
| 12 | added: port_in_redirect off; | |
| 13 | added: | |
| 14 | added: # Docusaurus erzeugt /pfad/index.html — sauberes URL-Routing inkl. Slash-Varianten. | |
| 15 | added: location / { | |
| 16 | added: try_files $uri $uri/ $uri.html /index.html; | |
| 17 | added: } | |
| 18 | added: | |
| 19 | added: # Gehashte Assets aggressiv cachen (Docusaurus legt sie unter /assets/ ab) | |
| 20 | added: location /assets/ { | |
| 21 | added: expires 1y; | |
| 22 | added: add_header Cache-Control "public, immutable"; | |
| 23 | added: access_log off; | |
| 24 | added: } | |
| 25 | added: | |
| 26 | added: error_page 404 /404.html; | |
| 27 | added: | |
| 28 | added: gzip on; | |
| 29 | added: gzip_types text/plain text/css application/javascript application/json image/svg+xml; | |
| 30 | added: gzip_min_length 1024; | |
| 31 | added: } | |