PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Docker Compose production – full Magento 2 stack, secrets, health checks, deployment

by Henryk Tews / Tuesday, 01 October 2024 / Published in Środowiska

Running Magento 2 in production with Docker Compose requires more than just copying the development setup. Secrets management, health checks, zero-downtime deployment, and log aggregation are the gaps between “works on my machine” and “runs reliably in production”. I show a complete production-grade docker-compose configuration with all these pieces in place.

Production stack overview

# docker-compose.prod.yml - services overview
services:
  nginx:        # web server, SSL termination
  php-fpm:      # PHP 8.4 FPM
  db:           # MariaDB 10.6
  redis-cache:  # object cache
  redis-fpc:    # full page cache
  redis-session:# session storage
  opensearch:   # search engine (Magento 2.4.8+)
  varnish:      # reverse proxy cache
  cron:         # Magento cron (separate container)
  queue:        # queue consumer

Secrets management – never put credentials in compose files

# Use Docker secrets (swarm) or .env files with restricted permissions
# .env.prod - NOT in version control, permissions 600

# docker-compose.prod.yml
services:
  db:
    image: mariadb:10.6
    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_root_password
      - db_password
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt
  db_password:
    file: ./secrets/db_password.txt

PHP-FPM with health check

  php-fpm:
    build:
      context: .
      dockerfile: docker/php/Dockerfile.prod
      args:
        PHP_VERSION: "8.4"
    environment:
      MAGENTO_MODE: production
      PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0
    volumes:
      - app_code:/var/www/html:ro  # read-only in production
      - magento_var:/var/www/html/var
      - magento_pub_media:/var/www/html/pub/media
    healthcheck:
      test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M
    depends_on:
      db:
        condition: service_healthy
      opensearch:
        condition: service_healthy

nginx with SSL

  nginx:
    image: nginx:1.26-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/prod.conf:/etc/nginx/conf.d/default.conf:ro
      - ./docker/nginx/ssl:/etc/nginx/ssl:ro  # cert and key
      - app_code:/var/www/html:ro
      - magento_pub_media:/var/www/html/pub/media:ro
      - magento_pub_static:/var/www/html/pub/static:ro
    healthcheck:
      test: ["CMD", "nginx", "-t"]
      interval: 30s
    depends_on:
      php-fpm:
        condition: service_healthy

Zero-downtime deployment script

#!/bin/bash
# deploy.sh - zero-downtime deployment

set -euo pipefail

COMPOSE="docker compose -f docker-compose.prod.yml"
APP_CONTAINER="magento_php-fpm_1"

echo "=== Starting deployment $(date) ==="

# 1. Pull new code (using volume or bind mount approach)
git pull origin main

# 2. Install dependencies in temp container
$COMPOSE run --rm php-fpm composer install \
    --no-dev --optimize-autoloader --no-interaction

# 3. Enable maintenance mode
$COMPOSE exec php-fpm bin/magento maintenance:enable

# 4. Run database upgrades
$COMPOSE exec php-fpm bin/magento setup:upgrade --keep-generated

# 5. Recompile DI
$COMPOSE exec php-fpm bin/magento setup:di:compile

# 6. Deploy static content
$COMPOSE exec php-fpm bin/magento setup:static-content:deploy \
    pl_PL en_US -f --jobs=4

# 7. Flush cache
$COMPOSE exec php-fpm bin/magento cache:flush

# 8. Disable maintenance
$COMPOSE exec php-fpm bin/magento maintenance:disable

# 9. Graceful PHP-FPM reload (no dropped requests)
$COMPOSE exec php-fpm kill -USR2 1

echo "=== Deployment complete $(date) ==="

Cron and queue consumer as separate containers

  cron:
    build:
      context: .
      dockerfile: docker/php/Dockerfile.prod
    command: ["bash", "-c", "while true; do php /var/www/html/bin/magento cron:run; sleep 60; done"]
    environment:
      MAGENTO_MODE: production
    volumes:
      - app_code:/var/www/html:ro
      - magento_var:/var/www/html/var
    depends_on:
      php-fpm:
        condition: service_healthy
    restart: unless-stopped

  queue-consumer:
    build:
      context: .
      dockerfile: docker/php/Dockerfile.prod
    command: >
      php /var/www/html/bin/magento
      queue:consumers:start async.operations.all
      --max-messages=10000
    environment:
      MAGENTO_MODE: production
    volumes:
      - app_code:/var/www/html:ro
      - magento_var:/var/www/html/var
    restart: unless-stopped
    depends_on:
      php-fpm:
        condition: service_healthy

Log aggregation

# Log driver - send all container logs to a centralised log service
# Example: using the json-file driver with rotation, or fluentd

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "5"
    tag: "{{.Name}}/{{.ID}}"

services:
  php-fpm:
    logging: *default-logging
    # ... other config

  nginx:
    logging: *default-logging
    # nginx logs also go to volumes for access log analysis
    volumes:
      - nginx_logs:/var/log/nginx

Summary

Production Docker Compose for Magento 2 has three non-negotiable elements beyond the basic service definitions: secrets management (never credentials in compose files), health checks (so dependent services wait for real readiness), and zero-downtime deployment (maintenance mode + cache flush + graceful FPM reload). Separate containers for cron and queue consumers give independent restart policies and resource limits, preventing a runaway import from killing the web server.

About Henryk Tews

What you can read next

Blackfire – DDEV setup, HTTP and CLI profiling, CI/CD assertions
Varnish – VCL, cache tagging, ESI, invalidation, DDEV setup
Docker from scratch – Dockerfile, nginx, docker-compose, Xdebug 3.x

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}