Shopware 6: Deployment-Pipeline
Auch wenn Shopware 6.6 mittlerweile nicht mehr die aktuellste Version ist und ich derzeit nicht mehr überwiegend mit Shopware arbeite, habe ich meine GitLab-Pipeline für ein möglichst schlankes und robustes Shopware-6.6-Deployment so aufbereitet, dass ich sie hier veröffentlichen kann.
Die Pipeline lässt sich problemlos auch auf andere Shopware-6-Versionen anpassen (eine Variante für 6.4 lief bereits produktiv, bis sie per AI in GitHub Actions migriert wurde).
Für die lokale Entwicklung genügt ein sehr schlankes Projekt:
custom
apps
plugins
static-plugins
env_config
prod
stage
.env.local
auth.json
composer.json
composer.lock
docker-compose.yml
Die docker-compose.yml kann eine einfache Dockware-Variante sein, in der alle relevanten Ordner als Volumes gemountet werden, sodass man direkt darin entwickeln kann.
In env_config liegt alles, was für die jeweiligen Umgebungen spezifisch ist. Dateien aus diesem Verzeichnis werden übernommen und ergänzen oder überschreiben vorhandene Konfigurationen. Typischerweise findet man hier eine passende .env.local sowie verschiedene Konfigurationsdateien für den config-Ordner.
Die Einrichtung auf dem Server ist unkompliziert: Im gewünschten Verzeichnis wird folgendes Skript ausgeführt:
mkdir shopware
cd shopware
mkdir config
mkdir releases
mkdir shared
cd shared
mkdir media
mkdir thumbnail
mkdir bundles
mkdir files
mkdir log
mkdir sitemap
cd config
mkdir jwt
cd ../..
cd releases
mkdir _init
cd _init
mkdir public
cd public
echo "<html><body>INIT</body></html>" > index.php
cd ../../..
ln -sfn ./releases/_init/public current
Anschließend kann der VHost auf current/public/ zeigen – die INIT-Seite sollte dann bereits erreichbar sein.
Die eigentliche Pipeline besteht aus zwei Dateien: der .gitlab-ci und der rebuild.sh.
In der .gitlab-ci.yml werden sämtliche Dateien zusammengeführt, ohne dass Shopware-Code ausgeführt wird. Alles, was Shopware und eine Datenbank erfordert, wird in der rebuild.sh erledigt.
In der .gitlab-ci.yml sind folgende Variablen erforderlich:
* server
* server_folder
* ssh_user
* target_env
* SSH_PRIVATE_KEY
Falls zwischen Umgebungen unterschieden werden muss (z. B. unterschiedliche Server-Adressen für Stage und Prod), lassen sich hier Environments einsetzen. Die Bezeichnungen müssen exakt mit den Verzeichnisnamen in env_config übereinstimmen.
.gitlab-ci.yml
stages:
- deploy
.default_deploy:
image: kroniak/ssh-client:3.21
stage: deploy
script:
- echo "init start"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo "init finished"
- apk add --no-cache zip
- zip -rq custom_$CI_PIPELINE_IID.zip custom
- cd env_config/$target_env/ && zip -rq ../../env_config_$CI_PIPELINE_IID.zip . -x "../*" && cd ../..
- ssh ${ssh_user}@${server} "cd ${server_folder} && composer create-project shopware/production ./releases/$CI_BUILD_TOKEN "v6.6.10.3" --no-interaction --ignore-platform-reqs"
- scp custom_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp env_config_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp composer.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.json
- scp composer.lock ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.lock
- scp auth.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/auth.json
- scp rebuild.sh ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/rebuild.sh
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq custom_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq env_config_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm custom_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm env_config_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf media && ln -sfn ../../../shared/media media"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf thumbnail && ln -sfn ../../../shared/thumbnail thumbnail"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf sitemap && ln -sfn ../../../shared/sitemap sitemap"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf bundles && ln -sfn ../../../shared/bundles bundles"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/var && rm -rf log && ln -sfn ../../../shared/log log"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && rm -rf files && ln -sfn ../../shared/files files"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && ./rebuild.sh"
- ssh ${ssh_user}@${server} "cd ${server_folder} && ln -sfn ./releases/$CI_PIPELINE_IID current"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd releases && ls -dt */ | tail -n +4 | xargs rm -rf && cd .."
tags:
- runner-docker
deploy_stage:
extends: .default_deploy
environment: stage
only:
- /^staging_(.+)$/
deploy_prod:
extends: .default_deploy
environment: prod
only:
- /^prod_(.+)$/
Das Skript rebuild.sh kann projektspezifisch angepasst werden, z. B. wenn zusätzliche Node.js-Versionen oder Build-Schritte erforderlich sind.
rebuild.sh
bin/console system:update:prepare
composer composer install
bin/console system:update:finish
bin/console cache:clear
bin/console database:migrate --all
bin/console cache:clear
npm install --prefix ./vendor/shopware/administration/Resources/app/administration
bin/build-administration.sh
bin/build-storefront.sh
bin/console sw:theme:compile
bin/console cache:clear
bin/console cache:warmup
bin/console http:cache:warm:up
--
Zum Schluss noch ein Beispiel für eine docker-compose.yml, die sich bei Bedarf leicht anpassen lässt:
version: "3"
services:
# shopware_full:
# build: ./infrastructure
# ports:
# - "80:80"
# - "443:443"
# - "2222:22"
# volumes:
# - ./infrastructure/my_vhost.conf:/etc/apache2/sites-enabled/000-default.conf:rw
# - ./shopware:/var/www/html
# networks:
# - web
shopware:
image: dockware/dev:6.6.10.3
ports:
- "80:80"
- "443:443"
- "2222:22"
volumes:
- "./custom/apps:/var/www/html/custom/apps"
- "./custom/plugins:/var/www/html/custom/plugins"
- "./custom/static-plugins:/var/www/html/custom/static-plugins"
- "./vendor:/var/www/html/vendor"
- "./composer.json:/var/www/html/composer.json"
- "./composer.lock:/var/www/html/composer.lock"
- "./env_config/dev/.env:/var/www/html/.env"
- "./env_config/dev/.env.local:/var/www/html/.env.local"
- "lang_mysql:/var/lib/mysql"
- "./docker/volumes/media:/var/www/html/public/media"
- "./docker/volumes/theme:/var/www/html/public/theme"
- "./docker/volumes/bundles:/var/www/html/public/bundles"
- "./config/packages/cache.yaml:/var/www/html/config/packages/cache.yaml"
networks:
- web
environment:
# default = 0, recommended to be OFF for frontend devs
- XDEBUG_ENABLED=0
- XDEBUG_REMOTE_HOST=172.17.0.1
# default = latest PHP, optional = specific version
- PHP_VERSION=8.2
- NODE_VERSION=20
#config host:mail, port:1025, user:demo, password:demo
mail:
image: mailhog/mailhog
networks:
- web
ports:
- 8025:8025
# use init or bin/console system:install --force to init database
shopware_mysql:
image: bitnami/mysql:8.4.5
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shopware
MYSQL_USER: app
MYSQL_PASSWORD: app
networks:
- web
volumes:
- ./docker/dumps/:/docker-entrypoint-initdb.d
- mysql_data:/var/lib/mysql
# shopware_mysql:
# image: bitnami/mariadb:10.6.20
# environment:
# MARIADB_ROOT_PASSWORD: root
# MARIADB_DATABASE: shopware
# MARIADB_USER: app
# MARIADB_PASSWORD: app
# networks:
# - web
# volumes:
# - ./infrastructure/dumps/:/docker-entrypoint-startdb.d
# - mysql_data:/bitnami/mariadb
shopware_redis:
image: redis:7
networks:
- web
shopware_elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
networks:
- web
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
volumes:
lang_mysql:
mysql_data:
elasticsearch_data:
networks:
web:
external: false