From 43a0b96fdf65c57942d131c6902faa71fe44bd4e Mon Sep 17 00:00:00 2001 From: DanielS Date: Wed, 11 Nov 2026 22:40:07 +0100 Subject: [PATCH] Test server: code in a Docker volume instead of a C:\ bind mount With Docker Desktop on Windows the bind-mounted C:\hotel made every page take ~5-7 s; with the code on the Linux filesystem it takes ~0.1 s. - docker/Dockerfile.server bakes the repo into the image (.dockerignore keeps .env, settings.inc.php and caches out) - docker/server-entrypoint.sh copies the code into the app_code volume on start: pass 1 updates code but skips runtime folders (uploads, generated images, caches, downloaded translations, settings.inc.php), pass 2 adds missing files without overwriting - docker/php-server.ini: OPcache without timestamp checks, larger realpath cache - setup script waits for the container and checks the install marker inside the volume; .gitattributes keeps *.sh LF on Windows checkouts - TESTSERVER.md: speed section and one-time switch from the first version Tested with a fresh stack: full install + demo + hardening, re-run keeps DB/uploads, rebuilt image delivers code changes. Co-Authored-By: Claude Opus 5.5 --- .dockerignore | 9 +++++++++ .gitattributes | 2 ++ docker-compose.server.yml | 12 ++++++++++++ docker/Dockerfile.server | 27 +++++++++++++++++++++++++++ docker/TESTSERVER.md | 18 +++++++++++++++++- docker/php-server.ini | 15 +++++++++++++++ docker/server-entrypoint.sh | 26 ++++++++++++++++++++++++++ docker/setup-testserver.ps1 | 15 ++++++++++++--- 8 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 docker/Dockerfile.server create mode 100644 docker/php-server.ini create mode 100644 docker/server-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..973b80a6b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +# Build context for docker/Dockerfile.server (repo root). +# Runtime data and secrets stay out of the image. +.git +.env +.htaccess +config/settings.inc.php +cache/smarty/compile/* +cache/smarty/cache/* +cache/class_index.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..503c6ae8c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Shell scripts run inside Linux containers: always LF +*.sh text eol=lf diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 04d731297..6aae614a3 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -3,14 +3,26 @@ # the app only listens on APP_BIND (default: all interfaces, port 8080). # docker compose -f docker-compose.yml -f docker-compose.server.yml up -d --build # See docker/TESTSERVER.md and docker/nginx-qloapps.conf. +# +# Speed: the code is baked into the image and copied into the volume "app_code" +# (Linux filesystem) on every start. A bind mount from C:\ makes every page ~40x slower. +# After "git pull": run the setup script again (rebuilds the image and restarts). services: app: + build: + context: . + dockerfile: docker/Dockerfile.server ports: !override - "${APP_BIND:-0.0.0.0:8080}:80" + volumes: !override + - app_code:/var/www/html environment: QLO_SSL: ${QLO_SSL:-1} restart: unless-stopped db: restart: unless-stopped + +volumes: + app_code: diff --git a/docker/Dockerfile.server b/docker/Dockerfile.server new file mode 100644 index 000000000..92da2a032 --- /dev/null +++ b/docker/Dockerfile.server @@ -0,0 +1,27 @@ +# Server image: same PHP/Apache as docker/Dockerfile, plus the shop code baked in. +# On start, server-entrypoint.sh copies the code into the volume at /var/www/html +# (Linux filesystem). Docker Desktop bind mounts from C:\ are ~40x slower for PHP. +FROM php:8.3-apache + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libfreetype6-dev libjpeg62-turbo-dev libpng-dev libwebp-dev \ + libzip-dev libxml2-dev libicu-dev unzip rsync \ + && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ + && docker-php-ext-install -j"$(nproc)" pdo_mysql mysqli gd zip soap intl opcache \ + && a2enmod rewrite headers \ + && rm -rf /var/lib/apt/lists/* + +COPY docker/php.ini /usr/local/etc/php/conf.d/qloapps.ini +COPY docker/php-server.ini /usr/local/etc/php/conf.d/zz-server.ini +COPY docker/apache-hardening.conf /etc/apache2/conf-enabled/zz-hardening.conf +RUN sed -ri 's!AllowOverride None!AllowOverride All!g' /etc/apache2/apache2.conf + +COPY . /opt/qloapps-src +COPY docker/server-entrypoint.sh /usr/local/bin/server-entrypoint.sh +# Windows checkouts may carry CRLF line endings +RUN sed -i 's/\r$//' /usr/local/bin/server-entrypoint.sh && chmod +x /usr/local/bin/server-entrypoint.sh + +WORKDIR /var/www/html +ENTRYPOINT ["server-entrypoint.sh"] +CMD ["apache2-foreground"] diff --git a/docker/TESTSERVER.md b/docker/TESTSERVER.md index 390752f10..76cd77b42 100644 --- a/docker/TESTSERVER.md +++ b/docker/TESTSERVER.md @@ -70,6 +70,22 @@ nginx -t && systemctl reload nginx Wichtig ist `proxy_set_header X-Forwarded-Proto $scheme;` – daran erkennt QloApps HTTPS. Ohne die Zeile gibt es Umleitungsschleifen. +## Geschwindigkeit + +Der Code wird ins Image gebaut und beim Start in das Docker-Volume `app_code` (Linux-Dateisystem) kopiert. +`C:\hotel` dient nur als Git-Checkout. Liegt der Code dagegen per Bind-Mount auf `C:\`, braucht jede Seite +mehrere Sekunden statt ~0,1 s (gemessen: 4,7 s gegenüber 0,13 s). + +**Umstieg von der ersten Version** (Code noch auf `C:\hotel` eingebunden): einmal komplett neu aufsetzen, +die Testdaten gehen dabei verloren: + +```powershell +cd C:\hotel +git pull +docker compose -f docker-compose.yml -f docker-compose.server.yml down -v +powershell -ExecutionPolicy Bypass -File docker\setup-testserver.ps1 -ShopDomain 192.168.178.20:8080 -NoSsl +``` + ## Fehlersuche QloApps speichert Domain und HTTPS-Modus in der Datenbank und **leitet jeden Aufruf dorthin um**. @@ -98,7 +114,7 @@ docker compose -f docker-compose.yml -f docker-compose.server.yml exec -u www-da |---|---| | Status | `docker compose -f docker-compose.yml -f docker-compose.server.yml ps` | | Logs | `docker compose -f docker-compose.yml -f docker-compose.server.yml logs -f app` | -| Update | `git pull`, dann das Setup-Skript erneut ausführen | +| Update | `git pull`, dann das Setup-Skript erneut ausführen (baut das Image neu, Daten bleiben) | | Stoppen | `docker compose -f docker-compose.yml -f docker-compose.server.yml down` (Daten bleiben im Volume `db_data`) | | Backup DB | `docker compose exec db mariadb-dump -uroot -p qloapps > backup.sql` | diff --git a/docker/php-server.ini b/docker/php-server.ini new file mode 100644 index 000000000..6ca376647 --- /dev/null +++ b/docker/php-server.ini @@ -0,0 +1,15 @@ +; Test/production server tuning (mounted by docker-compose.server.yml). +; With Docker Desktop the code sits on a Windows drive mounted into the container, +; where every file check is slow. These settings avoid re-checking PHP files. +; After a code update (git pull) restart the app container: +; docker compose -f docker-compose.yml -f docker-compose.server.yml restart app + +opcache.enable = 1 +opcache.validate_timestamps = 0 +opcache.memory_consumption = 256 +opcache.interned_strings_buffer = 32 +opcache.max_accelerated_files = 30000 +opcache.save_comments = 1 + +realpath_cache_size = 8M +realpath_cache_ttl = 3600 diff --git a/docker/server-entrypoint.sh b/docker/server-entrypoint.sh new file mode 100644 index 000000000..b4a1af070 --- /dev/null +++ b/docker/server-entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Copy the code baked into the image into the web root volume, then start Apache. +# pass 1: update code; runtime folders (uploads, generated images, caches, downloaded +# translations, config/settings.inc.php) are skipped +# pass 2: add any file that is still missing (first start, new defaults), never overwrite +set -e + +SRC=/opt/qloapps-src/ +DST=/var/www/html/ + +echo "server-entrypoint: updating code in $DST" +rsync -a --chown=www-data:www-data \ + --exclude='/.git' --exclude='/.env' --exclude='/.htaccess' \ + --exclude='/config/settings.inc.php' --exclude='/config/xml/' \ + --exclude='/img/' --exclude='/upload/' --exclude='/download/' \ + --exclude='/cache/' --exclude='/log/' --exclude='/translations/' --exclude='/mails/' \ + --exclude='/modules/*/translations/' --exclude='/modules/*/views/img/' --exclude='/modules/*/logs/' \ + --exclude='/themes/*/lang/' --exclude='/themes/*/cache/' --exclude='/themes/*/modules/*/translations/' \ + "$SRC" "$DST" + +echo "server-entrypoint: adding missing files" +rsync -a --ignore-existing --chown=www-data:www-data \ + --exclude='/.git' --exclude='/.env' --exclude='/config/settings.inc.php' \ + "$SRC" "$DST" + +exec docker-php-entrypoint "$@" diff --git a/docker/setup-testserver.ps1 b/docker/setup-testserver.ps1 index 4e32e8dcb..0dbfdc608 100644 --- a/docker/setup-testserver.ps1 +++ b/docker/setup-testserver.ps1 @@ -81,10 +81,19 @@ $envVars = Read-EnvFile Step 'Build and start containers' Invoke-Docker @compose up -d --build -Step 'File permissions for www-data' -Invoke-Docker @compose exec -T app sh -c 'cd /var/www/html && chown -R www-data:www-data img mails modules translations cache config upload download themes override log' +Step 'Wait for the app container' +# the entrypoint first copies the code into the volume; wait until Apache answers +$ready = $false +for ($i = 0; $i -lt 60; $i++) { + & docker @compose exec -T app sh -c 'test -f /var/www/html/index.php && curl -s -o /dev/null http://127.0.0.1/robots.txt' 2>$null + if ($LASTEXITCODE -eq 0) { $ready = $true; break } + Start-Sleep -Seconds 2 +} +if (-not $ready) { throw 'App container did not come up; check: docker compose ... logs app' } -if (Test-Path config/settings.inc.php) { +# the code lives in the Docker volume, so check the install marker inside the container +& docker @compose exec -T app test -f /var/www/html/config/settings.inc.php +if ($LASTEXITCODE -eq 0) { Step 'QloApps already installed (config/settings.inc.php exists) - skipping install' } else { Step 'Install QloApps (German, newsletter off)'