---
title: Speicher
description: Konfigurieren Sie die zwei S3-kompatiblen Buckets für Avatare, Organisationslogos und Chat-Anhänge, einschließlich der CORS-Regel, die beide Buckets brauchen.
---

Cargo Marshal speichert hochgeladene Bilder in zwei S3-kompatiblen Buckets. Diese Seite richtet sich an Betreiber, die die App selbst hosten oder bereitstellen. Sie erklärt, was jeder Bucket enthält, und nennt die CORS-Regel, die beide Buckets brauchen.

## Der öffentliche Bucket für Bilder

Der öffentliche Bucket enthält Avatare und Organisationslogos. Jeder, der die URL kennt, kann ein Objekt lesen. Die App liefert diese Dateien direkt von `NEXT_PUBLIC_S3_PUBLIC_URL` aus, und der Bucket-Name steht in `NEXT_PUBLIC_S3_BUCKET_NAME_IMAGES`. Sind beide Variablen nicht gesetzt, bleiben Uploads für Avatare und Logos sichtbar, aber deaktiviert.

## Der private Bucket für Anhänge

Der private Bucket enthält Bild-Anhänge aus dem Chat. Er blockiert jeden öffentlichen Zugriff, und sein Name steht in `S3_BUCKET_NAME_ATTACHMENTS`. Die App prüft, ob der Betrachter den Chat lesen darf, und gibt dem Browser dann eine signierte GET-URL, die nach etwa 30 Minuten abläuft. Ist die Variable nicht gesetzt, sind Bild-Uploads im Chat deaktiviert. Verweisen Sie sie nie auf den öffentlichen Bucket für Bilder.

Beide Buckets nutzen dieselben Zugangsdaten (`AWS_ACCESS_KEY_ID` und `AWS_SECRET_ACCESS_KEY`) und denselben Endpunkt (`AWS_ENDPOINT_URL_S3`). Ist der Endpunkt nicht gesetzt, spricht die App mit AWS S3 in `AWS_REGION`. Setzen Sie `AWS_S3_FORCE_PATH_STYLE=true`, wenn Ihr Anbieter URLs im Path-Style braucht.

## Erforderliche Bucket-Konfiguration

Der Browser lädt jede Datei mit einer vorsignierten PUT-Anfrage direkt in den Bucket hoch und lädt Chat-Anhänge mit einer signierten GET-Anfrage. Beides sind Anfragen über Ursprungsgrenzen hinweg, deshalb muss jeder Bucket den CORS-Preflight beantworten. Ein Bucket ohne CORS-Regel lehnt den Preflight ab, und der Upload schlägt fehl. R2 antwortet auf den Preflight mit `403`.

Wenden Sie dieselbe Regel auf beide Buckets an. Tragen Sie jeden Origin ein, von dem die App ausgeliefert wird:

- Die Produktions-URL, mit und ohne `www`, wenn beide erreichbar sind.
- Jede URL einer Preview-Bereitstellung, zum Beispiel `https://cargomarshal-git-my-branch.vercel.app`.
- `http://localhost:3000` für die lokale Arbeit.

Ein Origin besteht aus Schema, Host und Port, ohne Pfad und ohne abschließenden Schrägstrich.

### Cloudflare R2

Öffnen Sie im Cloudflare-Dashboard den Bucket, gehen Sie zu **Settings** und fügen Sie eine CORS-Richtlinie mit diesem JSON hinzu. Sie können es auch an die R2-CORS-API senden.

```json
{
  "rules": [
    {
      "allowed": {
        "origins": [
          "https://www.cargomarshal.com",
          "https://cargomarshal.com",
          "http://localhost:3000"
        ],
        "methods": ["GET", "HEAD", "PUT"],
        "headers": ["*"]
      },
      "exposeHeaders": ["ETag"],
      "maxAgeSeconds": 3600
    }
  ]
}
```

### AWS S3 und andere S3-kompatible Anbieter

Senden Sie dieses JSON an `PutBucketCors`, zum Beispiel mit `aws s3api put-bucket-cors --bucket <name> --cors-configuration file://cors.json`.

```json
{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://www.cargomarshal.com",
        "https://cargomarshal.com",
        "http://localhost:3000"
      ],
      "AllowedMethods": ["GET", "HEAD", "PUT"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3600
    }
  ]
}
```

### Regel erzeugen und prüfen

Führen Sie `pnpm storage:cors` im Wurzelverzeichnis des Repositorys aus. Der Befehl liest `apps/web/.env`, oder `apps/web/.env.test`, wenn die erste Datei fehlt, und gibt für jeden konfigurierten Bucket beide JSON-Formen aus. Übergeben Sie ein `--origin`-Flag pro App-Origin. Ohne Flag verwendet der Befehl den Origin von `NEXT_PUBLIC_APP_URL`.

```bash
pnpm storage:cors --origin https://cargomarshal-git-my-branch.vercel.app
```

Um die echten Buckets zu testen, fügen Sie `--check` hinzu. Der Befehl sendet für jeden Origin einen lesenden Preflight (eine HTTP-`OPTIONS`-Anfrage, ohne Zugangsdaten und ohne Schreibzugriff). Er testet GET auf der öffentlichen URL, PUT auf dem Bucket für Bilder sowie PUT und GET auf dem privaten Bucket. Er gibt pro Probe eine PASS- oder FAIL-Zeile mit Begründung aus und endet mit Exit-Code 1, wenn eine Probe fehlschlägt.

```bash
pnpm storage:cors --check --origin https://cargomarshal-git-my-branch.vercel.app
```

## Der Ladebericht und das Logo

Der Ladebericht fragt ein hochgeladenes PNG- oder JPEG-Logo zuerst beim Bild-Endpunkt der App an, `/_next/image`. Dieser Endpunkt lädt die Datei auf dem Server. Die Anfrage bleibt deshalb auf demselben Ursprung und hängt nicht von der CORS-Regel des Buckets ab.

In diesen Fällen lädt der Bericht das Logo stattdessen direkt von seiner URL. Ein Logo im öffentlichen Bucket braucht dann GET in der Regel oben:

- Das Logo ist ein SVG, oder die Organisation hat kein Logo und nutzt ihr erzeugtes DiceBear-Zeichen. `/_next/image` liefert kein SVG aus.
- Der Bucket läuft unter einer privaten Adresse, zum Beispiel ein lokales MinIO auf `localhost`. Next.js lädt keine Bilder von privaten Adressen, deshalb schlägt `/_next/image` fehl.
- `/_next/image` schlägt aus einem anderen Grund fehl, zum Beispiel weil der Host des Buckets in der Bild-Allowlist fehlt.

Schlägt auch die direkte Anfrage fehl, enthält der Bericht kein Logo. Behalten Sie GET in der Regel für beide Buckets. Ohne die PUT-Regel schlagen außerdem Uploads fehl, und Chat-Anhänge brauchen GET auf dem privaten Bucket.
