DDEV świetnie sprawdza się lokalnie. Ale jak wygląda stack Magento 2 na produkcji w Docker Compose? Pokażę kompletną konfigurację: nginx, PHP-FPM, MariaDB, Redis (trzy instancje), Elasticsearch i Varnish – z separacją sieci, sekretami Docker, healthcheckami i strategią deploymentu. To nie jest tutorial dla początkujących, ale referencja dla kogoś kto chce postawić Magento na własnej infrastrukturze.
Architektura stosu
Internet
|
Varnish :80/:443 (FPC)
|
Nginx :8080 (web server / FastCGI proxy)
|
PHP-FPM :9000 (aplikacja Magento)
| | | | |
MariaDB Redis-Cache Redis-Session Redis-FPC Elasticsearch
:3306 :6379 :6380 :6381 :9200
docker-compose.yml – główny plik
# docker-compose.yml
version: '3.8'
services:
varnish:
image: varnish:7.4
volumes:
- ./varnish/magento.vcl:/etc/varnish/default.vcl:ro
ports:
- "80:6081"
environment:
VARNISH_SIZE: 512m
depends_on:
nginx:
condition: service_healthy
networks:
- frontend
restart: unless-stopped
nginx:
image: nginx:1.25-alpine
volumes:
- ./nginx/magento.conf:/etc/nginx/conf.d/default.conf:ro
- magento_code:/var/www/magento:ro
depends_on:
php:
condition: service_healthy
networks:
- frontend
- backend
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
php:
build:
context: ./php
dockerfile: Dockerfile
args:
PHP_VERSION: "8.3"
MAGENTO_VERSION: "2.4.7"
volumes:
- magento_code:/var/www/magento
- ./php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
environment:
MAGE_MODE: production
secrets:
- db_password
- redis_password
- magento_admin_password
depends_on:
db:
condition: service_healthy
redis-cache:
condition: service_healthy
elasticsearch:
condition: service_healthy
networks:
- backend
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
db:
image: mariadb:10.11
volumes:
- db_data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/conf.d/magento.cnf:ro
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: magento
MYSQL_USER: magento
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
- db_root_password
networks:
- backend
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
restart: unless-stopped
deploy:
resources:
limits:
memory: 4G
redis-cache:
image: redis:7-alpine
command: >
redis-server
--maxmemory 1gb
--maxmemory-policy allkeys-lru
--save ""
--appendonly no
--requirepass_file /run/secrets/redis_password
secrets:
- redis_password
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
redis-session:
image: redis:7-alpine
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy noeviction
--appendonly yes
--appendfsync everysec
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
restart: unless-stopped
redis-fpc:
image: redis:7-alpine
command: >
redis-server
--maxmemory 2gb
--maxmemory-policy allkeys-lru
--save ""
--appendonly no
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
restart: unless-stopped
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- xpack.security.enabled=false
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es_data:/usr/share/elasticsearch/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
restart: unless-stopped
deploy:
resources:
limits:
memory: 3G
reservations:
memory: 1G
volumes:
magento_code:
driver: local
db_data:
driver: local
es_data:
driver: local
secrets:
db_password:
file: ./secrets/db_password.txt
db_root_password:
file: ./secrets/db_root_password.txt
redis_password:
file: ./secrets/redis_password.txt
magento_admin_password:
file: ./secrets/magento_admin_password.txt
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # backend nie ma dostępu do internetu bezpośrednio
Dockerfile dla PHP-FPM
# php/Dockerfile
FROM php:8.3-fpm-alpine AS base
ARG MAGENTO_VERSION
# Zależności systemowe
RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev freetype-dev \
libzip-dev icu-dev libxslt-dev libsodium-dev \
oniguruma-dev git unzip patch
# Rozszerzenia PHP dla Magento
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd pdo_mysql zip intl xsl soap bcmath \
sockets opcache sodium
# Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/magento
# PHP-FPM konfiguracja
COPY php-fpm.conf /usr/local/etc/php-fpm.d/www.conf
# Healthcheck
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD php-fpm -t
# Production stage
FROM base AS production
# Kod Magento
COPY --chown=www-data:www-data . /var/www/magento
RUN composer install --no-dev --optimize-autoloader --no-interaction
USER www-data
Nginx konfiguracja dla Magento
# nginx/magento.conf
upstream fastcgi_backend {
server php:9000;
keepalive 32;
}
server {
listen 8080;
server_name _;
root /var/www/magento/pub;
index index.php;
autoindex off;
charset UTF-8;
# Bezpieczeństwo - ukryj wersję
server_tokens off;
# Logi ze strukturą JSON - łatwiejsze do parsowania przez ELK/Loki
access_log /dev/stdout json_combined;
error_log /dev/stderr warn;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check)\.php$ {
fastcgi_pass fastcgi_backend;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param MAGE_MODE production;
fastcgi_param MAGE_RUN_CODE base;
fastcgi_read_timeout 600;
fastcgi_send_timeout 600;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
}
location /pub/static/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
try_files $uri $uri/ =404;
}
# Zablokuj dostęp do wrażliwych plików
location ~ (\.php$|\.phtml$) { deny all; }
location ~ /\.ht { deny all; }
location ~ /\.git { deny all; }
# Health check endpoint dla load balancera
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
Deployment – rolling update bez downtime
#!/bin/bash # deploy.sh - deployment bez downtime set -e echo "=== Magento 2 Deployment ===" # 1. Włącz maintenance mode PRZED budowaniem docker compose exec php bin/magento maintenance:enable # 2. Zbuduj nowy obraz PHP z nowym kodem docker compose build php # 3. Zatrzymaj stary PHP-FPM, uruchom nowy # Nginx i Varnish zostają - serwują cached content przez chwilę docker compose up -d --no-deps php # 4. Poczekaj aż PHP-FPM będzie healthy echo "Czekam na PHP-FPM..." timeout 120 bash -c 'until docker compose ps php | grep -q "healthy"; do sleep 2; done' # 5. Magento setup docker compose exec -T php bin/magento setup:upgrade --keep-generated docker compose exec -T php bin/magento setup:di:compile docker compose exec -T php bin/magento setup:static-content:deploy pl_PL en_US -f --jobs=$(nproc) # 6. Flush cache docker compose exec -T php bin/magento cache:flush # 7. Wyłącz maintenance mode docker compose exec php bin/magento maintenance:disable # 8. Purge Varnish FPC docker compose exec varnish varnishadm "ban req.url ~ ." echo "=== Deployment zakończony ==="
MySQL tuning dla Magento
# mysql/my.cnf [mysqld] # InnoDB - kluczowe ustawienia dla Magento innodb_buffer_pool_size = 2G # ~70% dostępnej RAM innodb_buffer_pool_instances = 4 innodb_log_file_size = 512M innodb_flush_log_at_trx_commit = 2 # wydajność vs bezpieczeństwo (0,1,2) innodb_flush_method = O_DIRECT innodb_file_per_table = 1 # Połączenia max_connections = 300 wait_timeout = 600 interactive_timeout = 600 # Query cache wyłączony w MySQL 8+ (usunięty) # Zamiast tego: Magento cache + Varnish # Slow query log slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 log_queries_not_using_indexes = 1
Monitorowanie healthchecków
# Sprawdź stan wszystkich serwisów
docker compose ps
# Live monitoring statusów healthchecków
watch -n 5 'docker compose ps --format "table {{.Service}}\t{{.Status}}\t{{.Health}}"'
# Alerty gdy serwis unhealthy - integracja z alertmanager lub prosta wersja:
docker compose events --filter type=health_status | while read event; do
if echo "$event" | grep -q "unhealthy"; then
# Wyślij alert (email, Slack, PagerDuty)
echo "ALERT: $(date) - $event" | mail -s "Magento Health Alert" ops@example.com
fi
done
Podsumowanie
Docker Compose na produkcji dla Magento 2 wymaga uwagi na kilka obszarów: separacja sieci (frontend/backend), Docker secrets dla haseł, healthchecki dla każdego serwisu, trzy osobne instancje Redis z właściwymi politykami eksmisji i MySQL tuning pod EAV schema Magento. Deployment bez downtime przez maintenance mode + rolling update PHP-FPM + Varnish purge to sprawdzony pattern. Dla większej skali i redundancji – Docker Swarm lub Kubernetes są naturalnym kolejnym krokiem.
