Hallo zusammen,
Stunden mühevoller Recherche und Arbeit stecken in dieser Anleitung. In der Hoffnung, dass sie dem einen oder anderem Zeit verschafft, die lieber in der Natur verbracht werden kann.
Mit dieser Anleitung sollten selbst Anfänger in der Lage sein, Shopware 6 mit Docker auf einem VPS zu installieren. Keine Garantie auf Vollständig- und Richtigkeit.
Konstruktiv kritische Kommentare und Verbesserungsvorschläge sehr gerne.
Viel Spaß und vor allem Erfolg!
Shopware 6 auf einem VPS mit Docker – Schritt für Schritt
Eine Anleitung für absolute Anfänger
Empfohlene Mindestausstattung: 4 vCores CPU · 4 GB RAM · 120 GB NVMe SSD · Debian 13 / PHP 8.4
HINWEIS Alle Befehle werden der Reihe nach in einem Terminal (SSH-Verbindung) eingegeben.
Platzhalter-Übersicht
Ersetze vor Beginn alle Platzhalter. Nutze „Alle ersetzen" (Strg+H / Cmd+H). Kopiere den Platzhalter aus der linken Spalte und ersetze ihn durch deinen Wert.
Server & Netzwerk
| Platzhalter | Beschreibung | Beispiel / Hinweis |
|---|---|---|
DEINE_SERVER_IP |
Öffentliche IP-Adresse des VPS | z. B. 203.0.113.10 |
DEINE_DOMAIN |
Domain des Shops (ohne https://) | z. B. mein-shop.de |
DEINE_MAIL |
E-Mail-Adresse (für SSH-Key & Certbot) | z. B. admin@mein-shop.de |
Linux-Benutzer
| Platzhalter | Beschreibung | Beispiel / Hinweis |
|---|---|---|
DEIN_LINUX_USER |
Neuer sudo-Benutzer auf dem Server | z. B. shopuser |
Datenbank
| Platzhalter | Beschreibung | Beispiel / Hinweis |
|---|---|---|
DEIN_DB_NAME |
Name der MariaDB-Datenbank | z. B. shopware_db |
DEIN_DB_USER |
MariaDB-Benutzername (nicht root) | z. B. sw_user |
DEIN_DB_PASSWORD |
Passwort für den DB-Benutzer | openssl rand -hex 32 |
DEIN_DB_ROOT_PW |
MariaDB root-Passwort | openssl rand -hex 32 |
Shopware Admin
| Platzhalter | Beschreibung | Beispiel / Hinweis |
|---|---|---|
DEIN_ADMIN_USER |
Benutzername des Shopware-Admins | z. B. shop-admin |
DEIN_ADMIN_PASSWORD |
Passwort des Shopware-Admins | min. 8 Zeichen, Groß/Klein/Zahl/Sonderz. |
TIPP
openssl rand -hex 32erzeugt ein sicheres Zufallspasswort. Die.env-Datei darf niemals in ein Git-Repository eingecheckt oder veröffentlicht werden!
Begriffserklärung
Für Einsteiger folgt eine kurze Übersicht der verwendeten Begriffe:
-
VPS (Virtual Private Server): Ein gemieteter virtueller Computer im Rechenzentrum. Verbindung per Internet.
-
SSH: Sichere verschlüsselte Verbindung vom eigenen PC zum Server (Fernzugriff übers Terminal).
-
Docker: Programm, das Anwendungen in abgeschlossenen Containern laufen lässt – sauber, reproduzierbar und leicht zu starten/stoppen.
-
Docker Compose: Werkzeug, das mehrere Container gleichzeitig steuert. Unser Shop besteht aus: Web-App, Datenbank, Worker (Hintergrundprozess) und Scheduler (Aufgabenplaner).
-
Shopware 6: Das Shop-System, das wir installieren. Läuft komplett im Docker-Container.
-
Nginx: Webserver, der Anfragen aus dem Internet entgegennimmt und an Shopware weiterleitet. Stellt auch das SSL-Zertifikat (https://) bereit.
-
Domain: Deine Internetadresse, z. B. mein-shop.de. Ohne Domain ist der Shop zunächst nur über die Server-IP erreichbar.
-
Termius: SSH-Client (App), mit der du dich von deinem lokalen Gerät auf den Server verbindest. Die kostenlose Version reicht aus.
Teil 0 – VPS mit Debian 13 installieren
Installiere Debian 13 auf deinem VPS über die Oberfläche deines Anbieters.
WARNUNG Port 22 muss zu Beginn freigeschaltet sein, damit der erste SSH-Zugang funktioniert.
Teil 1 – VPS absichern
Bevor wir irgendetwas installieren, machen wir den Server sicher. Ein frischer VPS ist im Internet sofort sichtbar und wird binnen Minuten von automatisierten Angriffen gescannt. Die folgenden Schritte schützen ihn auf ein solides Grundniveau.
Schritt 1 – System aktualisieren & Benutzer anlegen
Als erstes melden wir uns als root-User am Server an und aktualisieren alle vorinstallierten Programme. Anschließend legen wir einen eigenen Benutzer an – der root-Account hat unbegrenzte Rechte und sollte nicht für die tägliche Arbeit genutzt werden.
Der Benutzername in dieser Anleitung lautet DEIN_LINUX_USER. Tausche überall in der Anleitung diesen Platzhalter gegen deinen gewählten Namen aus.
apt update && apt upgrade -y \
&& adduser DEIN_LINUX_USER \
&& usermod -aG sudo DEIN_LINUX_USER
TIPP Du wirst nach einem Passwort für
DEIN_LINUX_USERgefragt. Wähle ein starkes Passwort und notiere es sicher. Die weiteren Fragen (Name, Telefon etc.) kannst du mit Enter überspringen.
Schritt 2 – SSH-Key-Authentifizierung einrichten
SSH-Keys sind sicherer als Passwort-Login: Du erzeugst ein Schlüsselpaar auf deinem lokalen PC, legst den öffentlichen Teil auf dem Server ab – und ab dann kommst nur du mit deinem privaten Schlüssel rein.
Auf Windows (PowerShell 7):
ssh-keygen -t ed25519 -C DEINE_MAIL
ssh-keygen -R DEINE_SERVER_IP; type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh DEIN_LINUX_USER@DEINE_SERVER_IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Verbindung testen:
ssh DEIN_LINUX_USER@DEINE_SERVER_IP
Schritt 3 – SSH-Konfiguration absichern
Jetzt deaktivieren wir den root-Login, schalten die Passwort-Anmeldung ab und verlegen SSH auf Port 2222 statt Standard 22. Das macht automatisierte Angriffe erheblich schwerer. Port 2222 muss ggf. noch in der Oberfläche deines VPS-Anbieters freigeschaltet werden.
WARNUNG WICHTIG: Lass deine aktuelle SSH-Verbindung (Termius-Session) offen! Öffne danach eine NEUE Verbindung in Termius, um zu prüfen, ob der Login noch klappt. Erst wenn der neue Login funktioniert, kannst du die alte Session schließen. Andernfalls sperrst du dich aus und musst den VPS zurücksetzen.
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config \
&& sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config \
&& sudo sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config \
&& sudo sed -i 's/^#\?Port .*/Port 2222/' /etc/ssh/sshd_config \
&& echo 'AllowUsers DEIN_LINUX_USER' | sudo tee -a /etc/ssh/sshd_config \
&& sudo systemctl restart sshd
Neue Verbindung im zweiten Termius-Tab testen (Port 2222 beachten!):
ssh -p 2222 DEIN_LINUX_USER@DEINE_SERVER_IP
TIPP Ab jetzt lautet dein SSH-Befehl immer:
ssh -p 2222 DEIN_LINUX_USER@DEINE_SERVER_IP– vergiss den Port nicht!
Schritt 4 – UFW-Firewall einrichten
UFW (Uncomplicated Firewall) erlaubt nur die Ports, die wir wirklich brauchen: 2222 (SSH), 80 (HTTP) und 443 (HTTPS). Alles andere wird geblockt.
sudo apt install ufw -y \
&& sudo ufw default deny incoming \
&& sudo ufw default allow outgoing \
&& sudo ufw allow 2222/tcp \
&& sudo ufw allow 80/tcp \
&& sudo ufw allow 443/tcp \
&& yes | sudo ufw enable \
&& sudo ufw status verbose
HINWEIS
yes | sudo ufw enablebestätigt die Aktivierung automatisch.ufw status verbosezeigt danach die aktiven Regeln – dort sollten die drei Ports 2222, 80 und 443 erscheinen.
WARNUNG Docker umgeht UFW standardmäßig über iptables direkt. Wenn MariaDB jemals mit
ports: 3306:3306in der compose.yaml exposed wird, ist sie von außen erreichbar – UFW sieht das nicht! Deshalb niemals den Datenbankport nach außen exponieren. In unserer Konfiguration ist das korrekt gelöst: Port 3306 ist in der compose.yaml bewusst nicht unterports:aufgeführt.
Schritt 5 – Fail2Ban installieren (Brute-Force-Schutz)
Fail2Ban überwacht die Login-Versuche auf dem Server. Nach 3 Fehlversuchen innerhalb von 10 Minuten wird die angreifende IP-Adresse für 1 Stunde gesperrt.
sudo apt install fail2ban -y \
&& sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
&& sudo tee -a /etc/fail2ban/jail.local > /dev/null << 'EOF'
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600
EOF
sudo systemctl enable fail2ban && sudo systemctl start fail2ban
Teil 2 – Docker installieren
Docker ist die Grundlage für unsere Shopware-Installation. Es ermöglicht, Shopware und die dazugehörige Datenbank in isolierten Containern zu betreiben – sauber, wiederherstellbar und einfach zu aktualisieren.
Schritt 1 – Automatische Sicherheitsupdates aktivieren
sudo apt install unattended-upgrades -y \
&& sudo dpkg-reconfigure -plow unattended-upgrades
HINWEIS Du wirst gefragt, ob automatische Updates aktiviert werden sollen – wähle Ja.
Schritt 2 – Docker installieren
Docker ist nicht in den Standard-Paketquellen von Debian enthalten. Wir fügen die offizielle Docker-Paketquelle hinzu und installieren Docker darüber.
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release \
&& sudo install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& sudo chmod a+r /etc/apt/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& sudo apt update \
&& sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
&& sudo systemctl enable docker && sudo systemctl start docker \
&& sudo usermod -aG docker DEIN_LINUX_USER
HINWEIS Der letzte Befehl (
usermod) gibtDEIN_LINUX_USERdie Berechtigung, Docker ohne sudo zu nutzen. Diese Änderung wird erst nach dem nächsten Ein- und Ausloggen wirksam.
Schritt 3 – Zeitzone, Dienste und Logwatch einrichten
sudo timedatectl set-timezone Europe/Berlin \
&& sudo systemctl disable bluetooth cups avahi-daemon 2>/dev/null; \
sudo apt install logwatch -y
TIPP Logwatch schickt täglich eine Zusammenfassung der Server-Aktivitäten per E-Mail – sofern E-Mail-Versand auf dem Server eingerichtet ist. Ohne E-Mail-Konfiguration speichert es die Berichte lokal.
Schritt 4 – Installation prüfen
Jetzt ausloggen und neu einloggen (damit die Docker-Gruppe wirksam wird), dann prüfen:
# Neu einloggen:
exit
ssh -p 2222 DEIN_LINUX_USER@DEINE_SERVER_IP
# Alles auf einmal prüfen:
sudo ufw status && sudo fail2ban-client status sshd && docker --version && docker compose version
TIPP Wenn alle vier Befehle ohne Fehlermeldung durchlaufen, ist die Basis fertig. Du siehst z. B.:
Docker version 27.x.xundDocker Compose version v2.x.x
Teil 3 – Shopware 6 installieren
Jetzt kommt der eigentliche Shop. Wir erstellen ein Shopware-Projekt mit Composer, bauen ein Docker-Image und starten alle nötigen Dienste mit Docker Compose.
HINWEIS Was ist Docker Compose? Docker Compose steuert mehrere Container gleichzeitig. Unser Shop besteht aus: der Web-App, der Datenbank, einem Worker (Hintergrundprozess) und einem Scheduler (Aufgabenplaner). Compose startet und verwaltet alle auf einmal.
Schritt 1 – Shopware-Projekt anlegen
Zuerst installieren wir Composer (PHP-Paketverwaltung) und die benötigten PHP-Erweiterungen. Dann erstellen wir das Shopware-Projekt.
sudo apt install -y php-cli unzip \
&& sudo apt install -y \
php8.4-curl php8.4-gd php8.4-xml php8.4-mbstring php8.4-zip \
php8.4-intl php8.4-mysql php8.4-pdo php8.4-opcache
# 1. Installer herunterladen
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
# 2. Erwarteten Hash von der offiziellen Quelle holen
EXPECTED_HASH=$(curl -s https://composer.github.io/installer.sig)
# 3. Tatsächlichen Hash der heruntergeladenen Datei berechnen
ACTUAL_HASH=$(php -r "echo hash_file('sha384', 'composer-setup.php');")
# 4. Vergleichen -- bei Abweichung Abbruch
if [ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]; then
echo "FEHLER: Hash stimmt nicht überein! Die Datei könnte manipuliert sein. Abbruch."
rm composer-setup.php
exit 1
fi
echo "Hash verifiziert -- Composer wird installiert..."
# 5. Erst jetzt installieren
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
# 6. Installer aufräumen
rm composer-setup.php
# 7. Prüfen
composer --version
Die folgenden Befehle abschnittsweise eingeben!
# Verzeichnis erstellen und Rechte setzen
sudo mkdir -p /opt/shopware
sudo chown DEIN_LINUX_USER:DEIN_LINUX_USER /opt/shopware
# Zum Benutzer wechseln
su - DEIN_LINUX_USER
# Shopware-Projekt erstellen
cd /opt/shopware
# nächster Abschnitt
composer create-project shopware/production:^6.7.8.1 .
composer require shopware/docker
HINWEIS
su - DEIN_LINUX_USERwechselt in den Benutzer-Account. Mitexitgelangst du zurück. Die Composer-Befehle dauern einige Minuten – das ist normal.
Da wir einen dedizierten Worker-Container verwenden, deaktivieren wir den Admin Worker direkt. Die Datei wird beim Docker-Build automatisch mit ins Image kopiert.
update_mail_variables_on_send: false – Deaktiviert die Aktualisierung von Nutzungsstatistiken in Mail-Templates nach jedem Versand. Das reduziert unnötige Datenbankschreiboperationen. Nur relevant wenn du auswerten möchtest wie oft ein bestimmtes Mail-Template verwendet wurde – für den normalen Shopbetrieb ohne Auswirkung.
mkdir -p /opt/shopware/config/packages
cat > /opt/shopware/config/packages/z-shopware.yaml << 'EOF'
shopware:
admin_worker:
enable_admin_worker: false
mail:
update_mail_variables_on_send: false
EOF
Schritt 2 – Dockerfile erstellen
Das Dockerfile beschreibt, wie das Docker-Image für Shopware gebaut wird. Es basiert auf einem fertigen Shopware-Basis-Image und ergänzt unseren Shop-Code.
Zuerst in das richtige Verzeichnis wechseln:
cd /opt/shopware
cat > Dockerfile << 'EOF'
#syntax=docker/dockerfile:1
ARG PHP_VERSION=8.4
FROM ghcr.io/shopware/docker-base:${PHP_VERSION}-frankenphp AS base-image
FROM ghcr.io/shopware/shopware-cli:latest-php-${PHP_VERSION} AS shopware-cli
FROM shopware-cli AS build
ADD . /src
WORKDIR /src
RUN --mount=type=cache,target=/root/.composer \
--mount=type=cache,target=/root/.npm \
/usr/local/bin/entrypoint.sh shopware-cli project ci /src
FROM base-image
COPY --from=build --chown=82 --link /src /var/www/html
EOF
Schritt 3 – Docker Compose Konfiguration erstellen
Die von Composer generierte compose.yaml ist nur für lokale Entwicklung gedacht. Wir ersetzen sie durch eine Production-Konfiguration mit folgenden Diensten:
-
database: MariaDB-Datenbank, in der alle Shop-Daten gespeichert werden.
-
init-perm: Setzt einmalig die Dateirechte für geteilte Ordner.
-
init: Richtet Shopware ein (Datenbank-Migrationen, erste Installation).
-
web: Die eigentliche Shopware-Webanwendung, erreichbar auf Port 8000.
-
worker: Verarbeitet Hintergrundaufgaben (z. B. E-Mails, Importe, fehlgeschlagene Nachrichten).
-
scheduler: Führt regelmäßige Aufgaben aus (z. B. Preis-Updates, Cache-Bereinigung).
WARNUNG Bitte ersetze alle Platzhalter (
DEIN_DB_NAME,DEIN_DB_USER, etc.) gemäß der Platzhalter-Übersicht am Anfang des Dokuments!
rm -f compose.yaml compose.override.yaml \
&& cat > compose.yml << 'EOF'
x-shopware-environment: &shopware
environment:
APP_ENV: prod
APP_SECRET: "${APP_SECRET}"
APP_URL: "${APP_URL:-http://localhost:8000}"
DATABASE_URL: "mysql://DEIN_DB_USER:${MYSQL_PASSWORD:-DEIN_DB_PASSWORD}@database/DEIN_DB_NAME"
PHP_UPLOAD_MAX_FILESIZE: "64M"
PHP_POST_MAX_SIZE: "64M"
PHP_MAX_EXECUTION_TIME: "350"
PHP_MAX_INPUT_TIME: "350"
PHP_MEMORY_LIMIT: "512M"
SYMFONY_TRUSTED_PROXIES: "172.16.0.0/12"
DATABASE_HOST: "database"
JWT_PRIVATE_KEY: "${JWT_PRIVATE_KEY}"
JWT_PUBLIC_KEY: "${JWT_PUBLIC_KEY}"
SHOPWARE_SKIP_WEBINSTALLER: 1
INSTALL_LOCALE: "${INSTALL_LOCALE:-de-DE}"
INSTALL_CURRENCY: "${INSTALL_CURRENCY:-EUR}"
INSTALL_ADMIN_USERNAME: "${INSTALL_ADMIN_USERNAME:-DEIN_ADMIN_USER}"
INSTALL_ADMIN_PASSWORD: "${INSTALL_ADMIN_PASSWORD:-DEIN_ADMIN_PASSWORD}"
volumes:
- plugins:/var/www/html/custom/plugins
- files:/var/www/html/files
- theme:/var/www/html/public/theme
- media:/var/www/html/public/media
- thumbnail:/var/www/html/public/thumbnail
- sitemap:/var/www/html/public/sitemap
services:
database:
image: mariadb:11.4
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-DEIN_DB_ROOT_PW}"
MARIADB_USER: DEIN_DB_USER
MARIADB_PASSWORD: "${MYSQL_PASSWORD:-DEIN_DB_PASSWORD}"
MARIADB_DATABASE: DEIN_DB_NAME
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD:-DEIN_DB_ROOT_PW}"]
interval: 10s
timeout: 5s
retries: 5
init-perm:
image: alpine
<<: *shopware
command: >
chown 82:82
/var/www/html/files
/var/www/html/public/theme
/var/www/html/public/media
/var/www/html/public/thumbnail
/var/www/html/public/sitemap
init:
image: shopware-local
build:
context: .
<<: *shopware
entrypoint: ["php", "vendor/bin/shopware-deployment-helper", "run"]
depends_on:
database:
condition: service_healthy
init-perm:
condition: service_completed_successfully
web:
image: shopware-local
build:
context: .
<<: *shopware
restart: unless-stopped
depends_on:
init:
condition: service_completed_successfully
ports:
- "127.0.0.1:8000:8000"
worker:
image: shopware-local
build:
context: .
<<: *shopware
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 60s
depends_on:
init:
condition: service_completed_successfully
entrypoint: ["php", "bin/console", "messenger:consume",
"async", "low_priority", "failed",
"--time-limit=3600", "--memory-limit=512M"]
healthcheck:
test: ["CMD", "php", "bin/console", "messenger:stats"]
interval: 60s
timeout: 15s
retries: 3
scheduler:
image: shopware-local
build:
context: .
<<: *shopware
restart: unless-stopped
depends_on:
init:
condition: service_completed_successfully
entrypoint: ["php", "bin/console", "scheduled-task:run", "--time-limit=3600"]
healthcheck:
test: ["CMD", "php", "bin/console", "messenger:stats"]
interval: 60s
timeout: 15s
retries: 3
volumes:
plugins:
mysql-data:
files:
theme:
media:
thumbnail:
sitemap:
EOF
HINWEIS Worker-Konfiguration: Die
failed-Queue sorgt dafür, dass fehlgeschlagene Nachrichten erneut verarbeitet werden.--time-limit=3600lässt den Worker-Prozess bis zu 1 Stunde laufen, bevor Docker ihn sauber neu startet. Der Scheduler erhält ebenfalls--time-limit=3600, damit er bei einem Fehler nicht endlos hängt.
Schritt 4 – Konfiguration & Passwörter (.env-Datei erstellen)
Die .env-Datei ist die zentrale Konfigurationsdatei mit Shop-URL, Datenbankpasswörtern und kryptografischen Schlüsseln. Das folgende Skript erzeugt sie automatisch.
WARNUNG WICHTIG:
INSTALL_LOCALElegt die Systemsprache fest. Diese Einstellung kann nach der ersten Installation NICHT mehr geändert werden! Setze sie daher jetzt korrekt, bevor du in Schritt 5 mitdocker compose upstartest.
APP_SECRET=$(openssl rand -hex 32)
eval "$(docker run --rm ghcr.io/shopware/shopware-cli:latest project generate-jwt --env)"
cat > .env << EOF
APP_ENV=prod
APP_SECRET=${APP_SECRET}
APP_URL=http://DEINE_DOMAIN
MYSQL_ROOT_PASSWORD=DEIN_DB_ROOT_PW
MYSQL_PASSWORD=DEIN_DB_PASSWORD
JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY}
JWT_PUBLIC_KEY=${JWT_PUBLIC_KEY}
INSTALL_LOCALE=de-DE
INSTALL_CURRENCY=EUR
INSTALL_ADMIN_USERNAME=DEIN_ADMIN_USER
INSTALL_ADMIN_PASSWORD=DEIN_ADMIN_PASSWORD
EOF
echo "Die .env-Datei wurde erfolgreich erstellt."
WARNUNG Sicherheit: Öffne die
.envjetzt mit einem Texteditor (z. B.nano .env) und prüfe alle Werte. Tipp:openssl rand -hex 32generiert ein sicheres Zufallspasswort. Diese Datei darf niemals in ein Git-Repository eingecheckt oder veröffentlicht werden!
TIPP Was ist
APP_URL? Das ist die Adresse, unter der dein Shop erreichbar sein soll. Hast du noch keine Domain, trage zunächsthttp://DEINE_SERVER_IP:8000ein. Sobald eine Domain vorhanden ist und Nginx mit SSL läuft (Schritt 6), änderst du sie aufhttps://DEINE_DOMAIN.
framework.yaml erstellen, damit Admin-IP-Adressen im Wartungsmodus richtig funktionieren:
cat > /opt/shopware/config/packages/framework.yaml << 'EOF'
framework:
trusted_proxies: '%env(default::SYMFONY_TRUSTED_PROXIES)%'
trusted_headers:
- 'x-forwarded-for'
- 'x-forwarded-host'
- 'x-forwarded-proto'
- 'x-forwarded-port'
EOF
Schritt 5 – Shop bauen und starten
Jetzt bauen wir das Docker-Image und starten alle Dienste. Dieser Schritt dauert beim ersten Mal je nach Internetverbindung und Server-Leistung zwischen 5 und 20 Minuten – das ist normal!
Zuerst in das richtige Verzeichnis wechseln:
cd /opt/shopware
# Image bauen und alle Dienste starten
docker compose build --no-cache \
&& docker compose up -d \
&& docker compose ps
HINWEIS
--no-cachesorgt dafür, dass das Image komplett neu gebaut wird, ohne auf zwischengespeicherte Schichten zurückzugreifen. Das ist beim ersten Mal sinnvoll, dauert aber länger.
Logs beobachten (Strg+C zum Beenden):
docker compose logs -f
Sobald der init-Container mit „exit 0" abgeschlossen ist und der web-Container läuft, ist der Shop erreichbar:
-
Frontend:
http://DEINE_SERVER_IP:8000(oderhttps://DEINE_DOMAINnach Schritt 6) -
Admin-Backend:
http://DEINE_SERVER_IP:8000/admin
TIPP Falls der init-Container mit einem Fehler abbricht (Exit-Code 1):
docker compose logs initzeigt die genaue Fehlermeldung. Häufige Ursache: ein Fehler in der.env-Datei (z. B. fehlendes Passwort oder falscheAPP_URL).
Schritt 6 – Nginx als Reverse Proxy mit SSL einrichten
Im Produktivbetrieb soll der Shop über eine Domain mit https:// erreichbar sein. Dafür brauchen wir Nginx als Reverse Proxy und ein kostenloses SSL-Zertifikat von Let’s Encrypt (Certbot).
HINWEIS Was macht Nginx hier? Shopware läuft intern auf Port 8000. Nginx nimmt Anfragen auf Port 80 (HTTP) und 443 (HTTPS) entgegen und leitet sie an Shopware weiter. Certbot besorgt automatisch ein SSL-Zertifikat und erneuert es alle 90 Tage selbst.
Ersetze DEINE_DOMAIN in den folgenden Befehlen durch deine echte Domain.
WARNUNG Deine Domain muss bereits auf die IP-Adresse des Servers zeigen (DNS-Eintrag), bevor Certbot das Zertifikat ausstellen kann! Überprüfe das vorher mit:
ping DEINE_DOMAIN
sudo apt install -y nginx certbot python3-certbot-nginx
1. Zuerst mit HTTP:
sudo tee /etc/nginx/sites-available/shopware << 'EOF'
server {
listen 80;
server_name DEINE_DOMAIN;
client_max_body_size 64M;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Kein HSTS hier!
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/shopware /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
2. Certbot:
sudo certbot --nginx -d DEINE_DOMAIN
3. Certbot schreibt die Datei um, aber HSTS trägt es nicht ein. Das machst du danach manuell:
sudo nano /etc/nginx/sites-available/shopware
4. Die Datei sieht nach Certbot ungefähr so aus – du fügst HSTS nur im 443-Block ein:
server {
listen 80;
server_name DEINE_DOMAIN;
return 301 https://$host$request_uri; # von Certbot gesetzt
}
server {
listen 443 ssl;
server_name DEINE_DOMAIN;
ssl_certificate /etc/letsencrypt/live/DEINE_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DEINE_DOMAIN/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 64M;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # ← jetzt eintragen
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
Nach dem Speichern Nginx neu laden und APP_URL in der .env aktualisieren:
sudo nginx -t && sudo systemctl reload nginx
# APP_URL in .env auf HTTPS umstellen
sed -i 's|APP_URL=.*|APP_URL=https://DEINE_DOMAIN|' /opt/shopware/.env
docker compose down && docker compose up -d
Teil: UPDATE von Shopware
Hier die korrekte Version eintragen:
composer require shopware/production:6.7.8.2 shopware/core:6.7.8.2 --with-all-dependencies
docker compose down
docker compose build --no-cache
docker compose up init
docker compose up -d
docker compose exec web php bin/console system:update:finish
docker compose exec web php bin/console cache:clear
Teil: Backup Cronjob
Backup-Ordner mit korrekter Berechtigung erstellen:
# Verzeichnis anlegen, DEIN_LINUX_USER als Eigentümer
sudo mkdir -p /opt/backups
sudo chown DEIN_LINUX_USER:DEIN_LINUX_USER /opt/backups
sudo chmod 750 /opt/backups
Backup-Skript erstellen (speichert für 3 Monate – selbst auf die eigenen Wünsche anpassen!!):
sudo bash -c 'cat > /opt/backups/backup.sh << '"'"'EOF'"'"'
#!/bin/bash
# === Konfiguration ===
BACKUP_DIR="/opt/backups"
DATE=$(date +%Y%m%d)
APP_DIR="/opt/shopware"
RETENTION_DAYS=90
LOG_FILE="/opt/backups/backup.log"
# === Logging ===
exec > >(tee -a "$LOG_FILE") 2>&1
echo "=============================="
echo "Backup gestartet: $(date)"
echo "=============================="
# === Verzeichnis erstellen ===
mkdir -p "$BACKUP_DIR"
# === Datenbank-Backup ===
echo "[DB] Starte MariaDB-Dump..."
cd "$APP_DIR" || { echo "APP_DIR nicht gefunden: $APP_DIR"; exit 1; }
DB_PASS=$(grep -oP '^MYSQL_PASSWORD=\K.*' .env)
docker compose exec -T database mariadb-dump \
-u DEIN_DB_USER -p"${DB_PASS}" DEIN_DB_NAME \
> "${BACKUP_DIR}/backup_${DATE}.sql"
if [ $? -eq 0 ]; then
echo "[DB] Datenbank-Backup erfolgreich: backup_${DATE}.sql"
else
echo "[DB] FEHLER beim Datenbank-Backup!"
fi
# === Datei-Backup (Volumes) ===
echo "[Files] Starte Volume-Backup..."
docker run --rm \
-v shopware_files:/data/files \
-v shopware_media:/data/media \
-v shopware_theme:/data/theme \
-v shopware_plugins:/data/plugins \
-v "${BACKUP_DIR}:/backup" \
alpine tar czf "/backup/shopware_files_${DATE}.tar.gz" /data
if [ $? -eq 0 ]; then
echo "[Files] Datei-Backup erfolgreich: shopware_files_${DATE}.tar.gz"
else
echo "[Files] FEHLER beim Datei-Backup!"
fi
# Eigentümer der neuen Backup-Dateien auf deinen Benutzer setzen
chown DEIN_LINUX_USER:DEIN_LINUX_USER "${BACKUP_DIR}/backup_${DATE}.sql"
chown DEIN_LINUX_USER:DEIN_LINUX_USER "${BACKUP_DIR}/shopware_files_${DATE}.tar.gz"
chmod 640 "${BACKUP_DIR}/backup_${DATE}.sql"
chmod 640 "${BACKUP_DIR}/shopware_files_${DATE}.tar.gz"
# === Alte Backups löschen ===
echo "[Cleanup] Lösche Backups älter als ${RETENTION_DAYS} Tage..."
find "$BACKUP_DIR" -name "backup_*.sql" -mtime +${RETENTION_DAYS} -delete
find "$BACKUP_DIR" -name "shopware_files_*.tar.gz" -mtime +${RETENTION_DAYS} -delete
echo "Backup abgeschlossen: $(date)"
echo ""
EOF
chmod +x /opt/backups/backup.sh'
Backup testen:
sudo bash /opt/backups/backup.sh
Cronjob aktivieren (jeden Sonntag um 2 Uhr):
sudo apt install cron -y \
&& sudo systemctl enable cron \
&& sudo systemctl start cron
(sudo crontab -l 2>/dev/null; echo "0 2 * * 0 /opt/backups/backup.sh") | sudo crontab -
Prüfen ob es geklappt hat:
sudo crontab -l
Teil 4 – Referenz & Wartung
Nützliche Docker-Befehle
| Befehl | Beschreibung |
|---|---|
docker compose up -d |
Alle Dienste im Hintergrund starten |
docker compose stop |
Alle Dienste stoppen |
docker compose ps |
Status aller Container anzeigen |
docker compose logs -f |
Logs aller Dienste live anzeigen |
docker compose logs -f web |
Nur Logs des Web-Containers |
docker compose exec web bash |
Shell im Web-Container öffnen |
docker compose build --no-cache |
Image komplett neu bauen |
docker compose pull |
Neueste Basis-Images herunterladen |
Häufige Shopware CLI-Befehle
Diese Befehle werden alle im laufenden web-Container ausgeführt:
| Befehl | Beschreibung |
|---|---|
docker compose exec web php bin/console cache:clear |
Cache leeren |
docker compose exec web php bin/console theme:compile |
Theme neu kompilieren |
docker compose exec web php bin/console database:migrate --all |
Datenbank-Migrationen ausführen |
docker compose exec web php bin/console user:create admin --admin |
Admin-User erstellen |
docker compose exec web php bin/console system:update:finish |
Shopware-Update abschließen |
docker compose exec web php bin/console list |
Alle verfügbaren Befehle anzeigen |
Troubleshooting
| Problem | Lösung |
|---|---|
| Container startet nicht | docker compose logs init – zeigt den Fehler |
| Datenbankverbindung schlägt fehl | docker compose ps – ist der database-Container healthy? |
| Permission denied Fehler | docker compose up init-perm – Rechte neu setzen |
| Out of Memory | PHP_MEMORY_LIMIT in compose.yml erhöhen |
| 502 Bad Gateway (Nginx) | Ist der web-Container wirklich gestartet? |
| Langsame Performance | Redis als Cache/Session-Speicher ergänzen |
Security Checklist
Vor dem Go-Live – diese Punkte abhaken:
-
[ ] Starke, individuelle Passwörter in der
.envgesetzt -
[ ] SSL/TLS mit gültigem Zertifikat aktiv (https://)
-
[ ] Datenbankport 3306 nicht nach außen offen (UFW)
-
[ ] Docker und Images regelmäßig aktualisieren
-
[ ] Regelmäßige Backups eingerichtet
-
[ ] UFW-Firewall aktiv und korrekt konfiguriert
-
[ ]
APP_ENV=prodin der.envgesetzt -
[ ] Standard-Admin-Passwort geändert
-
[ ] SSH-Key-Login funktioniert, Passwort-Login deaktiviert