Einführung
Dieses Tutorial zeigt, wie man Django und die django-storages
-Bibliothek mit Hetzner Object Storage nutzt.
Es wird erklärt, wie man Django-Modelle erstellt, über die man Dateien hochladen kann, die entweder öffentliche oder private Zugriffsrechte besitzen. Wenn du eine Datei mit privaten Zugriffsrechten hochlädst, erzeugt Django automatisch eine Pre-Signed URL für dich.
Obwohl Hetzner Object Storage S3-kompatibel ist, gibt es einige Unterschiede, die du kennen musst, um den Service mit Django und django-storages
korrekt zu verwenden.
So sieht die Admin-Oberfläche am Ende aus:
Voraussetzungen
- Grundlegende Kenntnisse über Linux-Befehle
- Ein lokales, Debian-basiertes Gerät mit Python installiert und einem Browser, um auf
localhost:8000
zuzugreifen - Account bei Hetzner
Terminologie
-
Bucket
Ein Bucket ist ein Container zum Speichern von Objekten in Hetzner Object Storage. Im Gegensatz zu S3, fügt Hetzner den Bucket-Namen nicht als Präfix zum Domainnamen hinzu. Alle Buckets in einer Region werden unter derselben Domain bereitgestellt. Ein wichtiger Unterschied in Bezug auf die Sicherheit. Außerdem muss es beim Konstruieren der URLs und Endpunkte beachtet werden.
-
S3 Signature Version
Hetzner Object Storage ist nicht kompatibel mit der neuesten S3 Signature Version, die standardmäßig in der zugrundeliegenden boto3-Bibliothek verwendet wird. Das ist ein wesentlicher Unterschied zu S3 und muss bei der Nutzung von django-storages berücksichtigt werden.
-
Vorgefertigte Access Control Lists (ACLs)
Hetzner Object Storage unterstützt die Standard-ACLs
private
undpublic-read
. Dadurch können sowohl öffentliche als auch private Mediendateien bereitgestellt werden. -
boto3
boto3 ist die zugrundeliegende Bibliothek, die von django-storages verwendet wird, um mit S3-kompatiblen Diensten zu interagieren. Auch wenn du sie nicht direkt nutzen musst, ist sie nützlich für Fehlersuche und Debugging. Bei Problemen kannst du das boto3-Debug-Logging aktivieren, indem du folgenden Code in deine settings.py-Datei einfügst:
import boto3 boto3.set_stream_logger(name='botocore')
Damit erscheinen detaillierte Logs in deiner Konsole – hilfreich zum Verstehen der Abläufe im Hintergrund. Bei Problemen lohnt sich auch ein Blick in die boto3-Dokumentation für S3, die wesentlich ausführlicher ist als die django-storages-Dokumentation.
Schritt 1 – Erstellen eines Buckets und S3-Zugangsdaten
Öffne in der Hetzner Cloud Console ein bestehendes Projekt oder erstelle ein neues.
Schritt 1.1 – Erstellen eines Buckets
Gehe im linken Menü deines Projekts auf den Tab Object Storage
und erstelle einen neuen Bucket mit folgenden Werten:
Wert | Beschreibung |
---|---|
LOCATION |
Wähle einen Standort, z.B. "Falkenstein". |
NAME/URL |
Wähle einen Namen/URL für deinen Bucket, z.B. mybucket . Achte darauf, dass der Name eindeutig im gesamten Hetzner Object Storage sein muss. |
OBJECT LOCK |
Wähle Disabled . |
VISIBILITY |
Wähle Private . Das ist wichtig für private Mediendateien. |
Klicke auf Jetzt erstellen & kaufen
, um den Bucket zu erstellen.
Weitere Informationen findest du in der Dokumentation zum Erstellen eines Buckets.
Schritt 1.2 – Erstellen von S3-Zugangsdaten
Öffne im linken Menü deines Projekts den Tab Sicherheit
, gehe dann auf S3-Zugangsdaten
und klicke auf S3-Zugangsdaten generieren
.
Im Popup: Kopiere den ACCESS KEY
und den SECRET KEY
und speichere sie sicher ab. Du benötigst diese Werte später in deinem Django-Projekt.
Weitere Informationen findest du unter S3-Schlüssel generieren.
Schritt 2 – Erstellen eines Django-Projekts und einer App
Überprüfe auf deinem lokalen Gerät, ob Python installiert ist, mit python --version
oder python3 --version
. Ich empfehle Python 3.12
, aber das Tutorial sollte auch mit 3.10
oder neuer funktionieren. Falls deine Version älter ist als 3.10
oder Python nicht installiert ist, lade es hier herunter und installiere es.
Erstelle einen Ordner für dein Django-Projekt und initialisiere darin eine virtuelle Python-Umgebung. Du kannst dafür virtualenv
oder venv
verwenden.
Falls du virtualenv
noch nicht installiert hast, kannst du es mit folgendem Befehl installieren:
pip install virtualenv
Schritt 2.1 – Erstellen des Projektordners und der virtuellen Python Umgebung
Lege die Projektstruktur an:
mkdir example-project
cd example-project
python3 -m venv venv
Aktiviere die virtuelle Umgebung:
source venv/bin/activate
Unter Windows verwendest du stattdessen:
venv\Scripts\activate
Schritt 2.2 – Installieren von Django und weiteren Paketen und Starten eines neuen Projekts
Installiere Django und die benötigten Pakete. Erstelle dazu eine requirements.txt
-Datei mit folgendem Inhalt:
# requirements.txt
Django
django-environ
django-storages[boto3]
whitenoise[brotli]
Hinweise:
- django-environ wird verwendet, um Konfigurationswerte aus der Umgebung zu laden – nach den Best Practices des Twelve-Factor App-Manifests.
- Es unterstützt auch
.env
-Dateien für die lokale Entwicklung. Achte darauf, die.env
-Datei in deine.gitignore
-Datei aufzunehmen, damit sie nicht versehentlich ins Git-Repository gelangt. - WhiteNoise mit Brotli-Kompression wird verwendet, um statische Dateien in Produktionsumgebungen bereitzustellen. Brotli ist eine moderne Komprimierungsmethode, die bessere Ergebnisse als gzip liefert.
Installiere die Pakete und erstelle ein neues Django Projekt:
pip install -r requirements.txt
django-admin startproject example_project
Schritt 2.3 – Erstellen einer Django App
Wechsle in das Projektverzeichnis und erstelle eine Django App:
cd example_project
python manage.py startapp media_app
Teste, ob alles funktioniert, indem du den Django Entwicklungsserver startest:
python manage.py runserver
Öffne deinen Browser und gehe auf http://localhost:8000/
. Du solltest die Django-Willkommensseite sehen:
Beende den Server mit CTRL+C
.
Schritt 3 – Django-Einstellungen anpassen
Schritt 3.1 – Importe und Umgebungsvariablen laden
Öffne die Datei settings.py
in deinem Django-Projekt und füge ganz oben folgende Zeilen hinzu:
# settings.py (am Anfang der Datei)
import os
from pathlib import Path
import environ
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_NAME = os.path.basename(BASE_DIR)
env = environ.Env()
environ.Env.read_env(env_file=BASE_DIR.parent / '.env')
Hinweise:
environ
wird importiert und initialisiert, um die Umgebungsvariablen aus der.env
-Datei zu laden.PROJECT_NAME
wird auf den Namen des Projektordners gesetzt. Dieser wird verwendet, um Standardwerte zu erstellen.
Schritt 3.2 – Object Storage Einstellungen hinzufügen
example_project/
├── example_project/
│ └── settings.py
└── manage.py
Jetzt füge die Object Storage Einstellungen am Ende der settings.py
hinzu:
Lasse die Variablen für die S3-Informationen unverändert so stehen. Diese werden im nächsten Schritt bestimmt.
# settings.py (am Ende der Datei)
# Standard-Storage-Backends, wenn Hetzner Object Storage nicht verwendet wird
# whitenoise wird genutzt, um statische Dateien in der Produktion auszuliefern
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
},
}
LOCATION_PREFIX = ""
PUBLIC_MEDIA_LOCATION = f"{LOCATION_PREFIX}media"
PRIVATE_MEDIA_LOCATION = f"{LOCATION_PREFIX}private"
USE_S3_MEDIA = env.bool("USE_S3_MEDIA", default=False)
if USE_S3_MEDIA:
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default="")
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME", default=PROJECT_NAME)
# Gebe an "virtual" für https://<bucket_name>.<endpoint>.com
# Gebe an "path" für https://<endpoint>.com/<bucket_name>
# Hetzner Object Storage verwendet "virtual".
AWS_S3_ADDRESSING_STYLE = "virtual"
AWS_S3_ENDPOINT_URL = env(
"AWS_S3_ENDPOINT_URL",
default="https://fsn1.your-objectstorage.com"
)
AWS_DEFAULT_ACL = None # Standardmäßig private Dateien
LOCATION_PREFIX = env("LOCATION_PREFIX", default=f"{PROJECT_NAME}/")
PUBLIC_MEDIA_LOCATION = f"{LOCATION_PREFIX}media"
PRIVATE_MEDIA_LOCATION = f"{LOCATION_PREFIX}private"
MEDIA_URL = f"{AWS_S3_ENDPOINT_URL}/{AWS_STORAGE_BUCKET_NAME}/{LOCATION_PREFIX}media/"
STORAGES["default"] = {
"BACKEND": "media_app.storage_backends.PublicMediaStorage"
}
PRESIGNED_URL_EXPIRATION = env.int("PRESIGNED_URL_EXPIRATION", default=600)
Hinweise:
Description | |
---|---|
LOCATION_PREFIX |
Wird verwendet, um Dateipfade im Bucket voranzustellen – praktisch, wenn du denselben Bucket für mehrere Projekte nutzen willst. |
USE_S3_MEDIA |
Steuert, ob Hetzner Object Storage genutzt werden soll. Lokale Entwicklung kann den FileSystemStorage verwenden. |
AWS_STORAGE_BUCKET_NAME |
Name deines Buckets (aus Schritt 1). Du kannst den Namen deines Buckets angeben oder das Feld leer lassen, um den Projektnamen als Bucketnamen zu verwenden. |
AWS_S3_ENDPOINT_URL |
URL des Endpunkts deines Object Storage aus Schritt 1 (z.B. für Falkenstein https://fsn1.your-objectstorage.com ). |
AWS_DEFAULT_ACL |
Auf None gesetzt, damit Dateien standardmäßig privat sind (sicherer!). Das ist aus Sicherheitsgründen wichtig. |
MEDIA_URL |
Die URL für den Zugriff auf die Medien-Dateien. Diese wird aus der Endpunkt-URL und dem Bucketnamen zusammengesetzt. |
STORAGES["default"] |
Auf die Verwendung vom PublicMediaStorage Backend gesetzt, wenn USE_S3_MEDIA auf True gesetzt ist. Sonst wird FileSystemStorage verwendet. |
PRESIGNED_URL_EXPIRATION |
Definiert die Gültigkeitsdauer von Pre-Signed URLs (Standard: 600 Sekunden = 10 Minuten). |
PUBLIC_MEDIA_LOCATION PRIVATE_MEDIA_LOCATION |
Die Speicherorte für die öffentlichen und privaten Dateien. Mit diesen werden die URLs für die Dateien erstellt. |
MEDIA_URL |
Die URL für den Zugriff auf die Medien-Dateien. Diese wird aus der Endpunkt-URL und dem Bucketnamen zusammengesetzt. |
Tipp:
Wenn du denselben Namen für Bucket und Präfix belässt, kann es verwirrend wirken (z.B. example_project/example_project/media/...
). Wenn du nur ein Projekt pro Bucket nutzt, kannst Du LOCATION_PREFIX
leer lassen.
Schritt 3.3 – Installed Apps und Middleware anpassen
Öffne wieder die settings.py
und ergänze Folgendes unter den bereits vorhandenen INSTALLED_APPS
und MIDDLEWARE
:
# settings.py (in INSTALLED_APPS und MIDDLEWARE hinzufügen)
INSTALLED_APPS = [
# ... (am ende der Liste hinzufügen)
'whitenoise.runserver_nostatic', # WhiteNoise für statische Dateien im Development
'media_app',
]
MIDDLEWARE = [
# Direkt nach der SecurityMiddleware:
'whitenoise.middleware.WhiteNoiseMiddleware',
]
Schritt 4 – Erstellen einer .env
-Datei
Erstelle im Wurzelverzeichnis deines Django-Projekts eine Datei namens .env
.
example_project/
├── example_project/
├── manage.py
└── .env
Diese Datei enthält die geheimen Konfigurationswerte für dein Projekt:
# .env
#DJANGO_SECRET_KEY=your_secret_key
USE_S3_MEDIA=True
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_S3_ENDPOINT_URL=https://fsn1.your-objectstorage.com
AWS_STORAGE_BUCKET_NAME=your_bucket_name
#LOCATION_PREFIX=your_location_prefix (muss mit einem / enden)
AWS_REQUEST_CHECKSUM_CALCULATION="WHEN_REQUIRED"
AWS_RESPONSE_CHECKSUM_CALCULATION="WHEN_REQUIRED"
Hinweise:
Wert | Beschreibung |
---|---|
DJANGO_SECRET_KEY |
Ist dein geheimer Schlüssel für das Django-Projekt. Ich habe ihn hier als Kommentar gelassen, da es eine gute Praxis ist, diesen Wert nicht direkt im Quellcode (settings.py ) zu speichern. |
USE_S3_MEDIA |
Wird auf True gesetzt, um Hetzner Object Storage zu verwenden. |
AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY |
Sind die Zugangsdaten aus Schritt 1. |
AWS_S3_ENDPOINT_URL |
Ist der Endpunkt deines Buckets (z.B. Falkenstein). |
AWS_STORAGE_BUCKET_NAME |
Ist der Name deines Buckets. |
LOCATION_PREFIX |
Ist optional und wird verwendet, wenn du mehrere Projekte in einem Bucket verwalten willst. (Muss mit / enden!) |
AWS_REQUEST_CHECKSUM_CALCULATION AWS_RESPONSE_CHECKSUM_CALCULATION |
müssen auf "WHEN_REQUIRED" gesetzt sein. Ansonsten gibt es Fehler beim Hochladen von Dateien zu Hetzner Object Storage. |
Du kannst übrigens einen neuen DJANGO_SECRET_KEY
generieren mit:
django-admin shell -c """
from django.core.management.utils import get_random_secret_key;
print(get_random_secret_key())
"""
Wichtig:
Vergiss nicht, .env
in deine .gitignore
aufzunehmen, damit diese Datei nicht versehentlich in dein Repository gelangt.
Schritt 5 – Implementieren der Storage-Backends
Erstelle nun Storage Backends für die app. Erstelle dazu die Datei media_app/storage_backends.py
und füge folgenden Code ein:
# media_app/storage_backends.py
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from storages.backends.s3boto3 import S3Boto3Storage
settings.LOCATION_PREFIX
class BaseMediaStorage(S3Boto3Storage):
signature_version = "s3"
file_overwrite = False
custom_domain = False
class PublicMediaStorage(BaseMediaStorage):
location = settings.PUBLIC_MEDIA_LOCATION
default_acl = "public-read"
class PrivateMediaStorage(S3Boto3Storage):
location = settings.PRIVATE_MEDIA_LOCATION
default_acl = "private"
def get_private_file_storage():
if not settings.USE_S3_MEDIA:
return FileSystemStorage()
else:
return PrivateMediaStorage()
Hinweise:
Wert | Beschreibung |
---|---|
BaseMediaStorage |
Basisklasse, die gemeinsame Einstellungen wie signature_version = "s3" enthält. (Sehr wichtig, da Hetzner nicht S3v4 unterstützt!) |
PublicMediaStorage |
Für öffentliche Dateien gedacht (default_acl = "public-read" ). |
PrivateMediaStorage |
Für private Dateien gedacht (default_acl = "private" ). |
get_private_file_storage |
Eine Hilfsfunktion: Sie entscheidet, ob lokale Speicherung (FileSystemStorage ) oder Hetzner Object Storage (PrivateMediaStorage ) verwendet wird – je nachdem, ob USE_S3_MEDIA=True gesetzt ist. |
Schritt 6 – Erstellen von Django-Modellen mit FileFields
Erstelle nun Django-Modelle, um Referenzen zu den Dateien in der Datenbank zu speichern. Öffne dazu die Datei media_app/models.py
und füge folgenden Code ein:
# media_app/models.py
import os
import uuid
from django.conf import settings
from django.db import models
from .storage_backends import get_private_file_storage, PrivateMediaStorage
def _get_random_filename(instance, filename):
model_name = instance.__class__.__name__.lower()
ext = filename.split('.')[-1]
new_filename = f"{uuid.uuid4()}.{ext}"
return os.path.join(model_name, new_filename)
class PublicDocument(models.Model):
title = models.CharField(max_length=255)
file = models.FileField(upload_to=_get_random_filename)
class PrivateDocument(models.Model):
title = models.CharField(max_length=255)
file = models.FileField(
upload_to=_get_random_filename,
storage=get_private_file_storage
)
def get_presigned_url(self):
if settings.USE_S3_MEDIA:
storage = PrivateMediaStorage()
return storage.url(
self.file.name,
expire=settings.PRESIGNED_URL_EXPIRATION
)
return None
Was passiert hier?
Wert | Beschreibung |
---|---|
_get_random_filename |
Diese Hilfsfunktion erstellt einen zufälligen Dateinamen. Damit verhinderst du Namenskollisionen und potenzielle Sicherheitsprobleme durch benutzerdefinierte Dateinamen. |
PublicDocument |
Ein einfaches Modell für öffentliche Dateien. Nutzt implizit den Default-Storage (PublicMediaStorage , wenn USE_S3_MEDIA=True gesetzt ist). |
PrivateDocument |
Ein Modell für private Dateien. Nutzt explizit get_private_file_storage() , um den korrekten Storage Backend zu verwenden. |
get_presigned_url() |
Erzeugt eine Pre-Signed URL für den privaten Download. Gültig für die in PRESIGNED_URL_EXPIRATION definierte Zeit. |
Damit Django die neuen Modelle in die Datenbank übernimmt, führe Folgendes aus, um die Migrationen zu erstellen und anzuwenden:
python manage.py makemigrations
python manage.py migrate
Wenn alles geklappt hat, siehst du keine Fehler in der Konsole.
Schritt 7 – Erstellen einer Django-Admin-Oberfläche
Öffne die Datei media_app/admin.py
und füge folgenden Code ein:
# media_app/admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import PublicDocument, PrivateDocument
@admin.register(PublicDocument)
class PublicDocumentAdmin(admin.ModelAdmin):
list_display = ('title', 'file_url')
def file_url(self, obj):
return format_html(
'<a href="{}" target="_blank">Datei ansehen</a>',
obj.file.url
)
file_url.short_description = 'Dateilink'
@admin.register(PrivateDocument)
class PrivateDocumentAdmin(admin.ModelAdmin):
list_display = ('title', 'get_file_url')
readonly_fields = ('get_file_url',)
def get_file_url(self, obj):
url = obj.get_presigned_url()
return format_html(
'<a href="{}" target="_blank">Datei herunterladen</a>',
url
)
get_file_url.short_description = 'Download-Link'
Was passiert hier?
-
PublicDocumentAdmin:
- Fügt eine Spalte hinzu, die einen Link zur Datei anzeigt.
- Beim Klicken wird die Datei im neuen Tab geöffnet.
-
PrivateDocumentAdmin:
- Zeigt ebenfalls einen Link zur Datei an.
- Hier wird allerdings eine Pre-Signed URL erzeugt, die nur zeitlich begrenzt gültig ist.
- Private Dateien sind nicht direkt zugänglich, sondern nur über Pre-Signed URLs.
Schritt 8 – Testen der Implementierung
Jetzt kannst Du prüfen, ob alles wie geplant funktioniert!
Schritt 8.1 – Superuser erstellen und Server starten
Erzeuge zuerst einen Superuser, damit du dich im Django-Admin anmelden kannst:
python manage.py createsuperuser
Folge den Anweisungen und gib Benutzername, E-Mail und Passwort ein.
Starte dann den Django-Entwicklungsserver:
python manage.py runserver
Klicke hier, falls eine Fehlermeldung erscheint
Wenn eine Fehlermeldung wie
Error: That port is already in use.
angezeigt wird, kann das an dem Test in Schritt 2.3 liegen. Prüfe ob der Port tatsächlich von Python aus Schritt 2.3 genutzt wird. Wenn ja, kannst du den Prozess beenden und erneut versuchen den Django-Entwicklungsserver zu starten.lsof -i :8000 kill -9 <PID> python manage.py runserver
Öffne deinen Browser und gehe zu:
http://localhost:8000/admin/
Melde dich mit deinen Superuser-Zugangsdaten an.
Du solltest jetzt die beiden Modelle sehen:
- PublicDocument
- PrivateDocument
Schritt 8.2 – Öffentliche Dateien testen
- Klicke im Admin auf PublicDocument und erstelle ein neues Dokument.
- Lade eine Datei hoch und speichere sie.
- In der Listenansicht siehst du jetzt eine Spalte mit einem Link: "Datei ansehen".
- Klicke auf den Link – die Datei öffnet sich in einem neuen Tab.
- Kopiere die URL und öffne sie in einem privaten/incognito Browserfenster – die Datei sollte öffentlich erreichbar sein.
- Schau Dir die URL an – sie sollte den zufälligen Dateinamen direkt enthalten. Über diese URL ist die Datei immer abrufbar.
Schritt 8.3 – Private Dateien testen
- Klicke auf PrivateDocument und erstelle ein neues Dokument.
- Lade wieder eine Datei hoch und speichere sie.
- In der Listenansicht siehst du einen "Download-Link".
- Klicke auf den Link – die Datei wird geöffnet oder heruntergeladen.
- Kopiere die Pre-Signed URL.
- Öffne sie in einem privaten Browserfenster innerhalb von 10 Minuten. Die Datei sollte abrufbar sein.
- Warte länger als die eingestellte Ablaufzeit (
PRESIGNED_URL_EXPIRATION
, z.B. 600 Sekunden, bzw. 10 Minuten) und versuche es erneut. Jetzt solltest du eine Fehlermeldung sehen.
Schritt 8.4 – Kontrolle im Hetzner Object Storage
- Logge dich in die Hetzner Cloud Console ein.
- Gehe zu deinem Object Storage Bucket.
- Du solltest die hochgeladenen Dateien dort sehen – aufgeteilt in die Ordner
media
undprivate
.
Ergebnis
In diesem Tutorial hast du gelernt:
- Wie du Hetzner Object Storage mit Django und
django-storages
kombinierst, - Wie du sowohl öffentliche als auch private Dateien speicherst,
- Wie du Pre-Signed URLs für sichere Downloads nutzt.
Du hast jetzt eine solide Grundlage geschaffen – und dabei wichtige Dinge wie signature_version
, ACLs, Environment-Handling und Sicherheit berücksichtigt.
Checkliste
Zusammengefasst musst du sieben Punkte beachten, wenn du Hetzner Object Storage mit Django verwendest:
- Verwende einen privaten Bucket, wenn du private Dateien speichern möchtest.
- Nutze die korrekte S3-Signaturversion (
s3
). - Verwende die korrekte Endpunkt-URL und den richtigen Bucket-Pfad für Hetzner Object Storage.
- Setze Access Control Lists (ACLs) gezielt ein, um den Zugriff auf Dateien zu steuern.
- Verwende Pre-Signed URLs, um private Dateien sicher bereitzustellen.
- Setze
AWS_REQUEST_CHECKSUM_CALCULATION
undAWS_RESPONSE_CHECKSUM_CALCULATION
auf"WHEN_REQUIRED"
, um Upload-Fehler bei Hetzner Object Storage zu vermeiden. - Optional: Verwende Location Prefixes, um einen Bucket für mehrere Projekte zu nutzen.
Wie du vielleicht bemerkt hast, behandelt dieses Tutorial nicht die Verwendung von Object Storage für statische Medien-Dateien (wie CSS, JS, Bilder). Der Grund dafür ist, dass Hetzner Object Storage alle Buckets einer Region über dieselbe Domain bedient. Dadurch sind sichere CORS-Einstellungen (Cross-Origin Resource Sharing) nicht möglich: Man müsste pauschal allen Buckets auf dieser Domain vertrauen – was ein Sicherheitsrisiko darstellt (z.B. XSS-Angriffe).
Daher der Rat:
- Für statische Dateien lieber ein CDN (Content Delivery Network) verwenden oder
whitenoise
einsetzen, um statische Dateien direkt von deiner App auszuliefern.
Weitere Ressourcen
Sicherheitshinweis
Dieses Tutorial bietet ein funktionierendes Grundgerüst, aber für einen produktiven Einsatz solltest du unbedingt noch weitere Sicherheitsmaßnahmen einbauen, etwa:
- Validierung von Dateinamen (nur erlaubte Zeichen zulassen),
- Einschränkung auf erlaubte Dateitypen (z.B. nur Bilder, PDFs),
- Begrenzung der maximalen Dateigröße.
Außerdem solltest du sicherstellen, dass nur berechtigte Benutzer Zugriff auf Pre-Signed URLs erhalten. In diesem Beispiel geschieht das implizit, weil nur Admins Zugriff über die Django-Admin-Oberfläche haben.