Docker for Magento: PHP-FPM, Nginx, Node, Elasticsearch/OpenSearch and DB
AI generated
Docker · Magento 2 · PHP-FPM · Nginx · Elasticsearch · MariaDB
Docker for Magento
PHP-FPM, Nginx, Node, Elasticsearch and DB

Magento 2 needs a precisely tuned service stack. PHP-FPM, Nginx, MariaDB, Redis, Elasticsearch or OpenSearch, and a Node container for frontend builds: each of these services has specific configuration requirements that differ in Docker from a classic server installation. Knowing these differences is what lets you build a stable and performant Magento Docker stack.

16 min read PHP 8.4-FPM · Nginx · MariaDB 11 · Redis 7 · OpenSearch 2 Magento 2.4.8 · Hyvä Themes

1. The Magento Stack: Why So Many Services

Docker for Magento requires more services than a typical PHP application. Magento 2.4 does not run without a search engine: Elasticsearch or OpenSearch is mandatory for catalog search. Redis is technically optional, but in practice indispensable for session storage and the full page cache. Without Redis, Magento does not scale even under moderate traffic. MariaDB or MySQL is the primary data store. PHP-FPM processes requests behind Nginx. And Hyva themes need a Node container that compiles Tailwind CSS. The complete stack therefore comprises at least six containers.

The most important principle in a Docker Magento setup: each service is responsible for exactly one task. PHP-FPM processes PHP, Nginx serves static files directly and proxies PHP requests. MariaDB stores data, Redis caches it. This separation allows every service to be scaled and configured independently. The most common mistake is a combined PHP and Nginx container, which prevents independent scaling and makes health checks harder. Docker for Magento lets you enforce this separation consistently.

2. Configuring PHP-FPM Correctly

PHP-FPM in a Docker Magento setup needs a specific configuration that differs from a standard Debian or Ubuntu installation. The PHP-FPM container must listen on port 9000 or via a Unix socket. In Docker, the TCP port is easier because socket files would have to be shared through common volumes. The most important PHP settings for Magento are: memory_limit of at least 2G for Magento operations such as setup:di:compile, max_execution_time of at least 1800 for long CLI processes, and opcache, which must be enabled and correctly sized for production.

Hyva themes with Docker for Magento and PHP 8.4 need additional extensions: bcmath, gd, intl, pdo_mysql, soap, xsl and zip. These extensions must be installed in the Dockerfile of the PHP-FPM image. The Mark Shust Docker Magento stack uses a preconfigured image that contains all Magento extensions, and using the same image as a base is recommended for custom builds too. Xdebug belongs only in the development image and should be enabled through an override, not in the base configuration.


# Docker Compose service definitions for a complete Magento 2 stack
# Each service has a single responsibility: PHP processes PHP, Nginx serves static files

services:
  phpfpm:
    image: markoshust/magento-php:8.4-fpm-0
    networks: [magento]
    volumes:
      - magento_src:/var/www/html          # shared source volume
      - ./src/app/etc/php.ini.sample:/usr/local/etc/php/conf.d/99-magento.ini:ro
    environment:
      MAGENTO_RUN_MODE: production
      PHP_MEMORY_LIMIT: 2G
      PHP_MAX_EXECUTION_TIME: 1800
      PHP_OPCACHE_ENABLE: 1
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
      opensearch:
        condition: service_healthy
    restart: unless-stopped

  nginx:
    image: markoshust/magento-nginx:1.24-0
    networks: [magento]
    ports:
      - "80:8000"
      - "443:8443"
    volumes:
      - magento_src:/var/www/html:ro       # read-only for static file serving
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - phpfpm
    restart: unless-stopped

  # CLI container for Magento commands, same image as phpfpm but a different purpose
  cli:
    image: markoshust/magento-php:8.4-fpm-0
    networks: [magento]
    volumes:
      - magento_src:/var/www/html
    command: ["tail", "-f", "/dev/null"]   # keep container running for exec
    profiles: [cli]                        # only start when explicitly needed

3. Nginx as the Magento Frontend Proxy

Nginx in a Docker Magento setup has two jobs: serving static files directly from the filesystem and forwarding PHP requests to PHP-FPM. The Nginx configuration for Magento is complex. Magento ships an official nginx.conf.sample that serves as a starting point. The most important adjustments for Docker for Magento: the fastcgi_pass directive must point to the PHP-FPM container name (phpfpm:9000 instead of a Unix socket), and the root path must match the volume mount path.

A common mistake in a Docker Magento Nginx setup: the static content directory /pub/static does not exist in the Nginx container because only PHP-FPM has access to the source volume. The solution is to mount the shared volume in both PHP-FPM and Nginx: read-only in Nginx, read-write in PHP-FPM. This requires a named Docker volume instead of a host path mount, so both containers see the same file state. Gzip compression for JS, CSS and HTML should be enabled in the Nginx configuration, which noticeably reduces the amount of data transferred for Hyva themes.

4. MariaDB: Performance and Healthcheck

MariaDB in a Docker Magento stack needs a specific configuration that goes beyond the default image. The most important InnoDB parameters for Magento: innodb_buffer_pool_size should be 70 to 80 percent of available RAM, innodb_log_file_size at least 256M, max_allowed_packet at least 64M for large Magento imports. Passing these values as environment variables or as a custom config file via a volume mount is better than extending the image, since it keeps updates simple.

The healthcheck for MariaDB in Docker for Magento is critical, because PHP-FPM and Nginx should only start once the database is ready. The official MariaDB container includes a healthcheck.sh script designed exactly for this. Using depends_on: condition: service_healthy in PHP-FPM and other services that need the database ensures the correct startup order is respected. Without this mechanism, services start in parallel and fail while the database is still initializing.


# Database and caching services for Docker Magento
# MariaDB with performance tuning, Redis for two separate roles

  db:
    image: mariadb:11.4
    networks: [magento]
    volumes:
      - db_data:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d:ro   # custom InnoDB config
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: magento
      MYSQL_USER: magento
      MYSQL_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped

  # mysql/conf.d/magento.cnf: InnoDB tuning for Magento workloads
  # [mysqld]
  # innodb_buffer_pool_size = 2G
  # innodb_log_file_size = 256M
  # max_allowed_packet = 64M
  # innodb_flush_log_at_trx_commit = 2     # faster, slight durability trade-off

  redis:
    image: redis:7-alpine
    networks: [magento]
    command: ["redis-server", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  # Separate Redis instance for session storage (isolated from page cache)
  redis-session:
    image: redis:7-alpine
    networks: [magento]
    command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "noeviction"]
    restart: unless-stopped

5. Redis: Session and Full Page Cache

Redis in a Docker Magento setup fulfills two different roles: full page cache and session storage. These two use cases have different eviction policies and should ideally use separate instances. For the full page cache, allkeys-lru is correct: old pages get evicted once memory fills up. For sessions, noeviction is correct: sessions must never be deleted automatically, since that leads to unexpected logouts. Two separate Redis containers in Docker for Magento are simple to configure and avoid this problem entirely.

Magento 2.4 uses Redis through the env.php configuration. In a Docker Magento setup, the Redis hosts should point to the Compose service names: 'host' => 'redis' for the page cache and 'host' => 'redis-session' for sessions. Important: Redis does not persist data by default. For production environments, redis.conf should be configured with appendonly yes and a volume for the AOF file. In development, persistence is optional: a Redis restart is fast, and the cache rebuilds automatically.

6. Elasticsearch and OpenSearch: Catalog Search

Elasticsearch or OpenSearch is the most resource hungry service in a Docker Magento stack. Elasticsearch needs at least 1 GB of heap, OpenSearch at least 512 MB. The most common mistake is a missing vm.max_map_count setting on the host: Elasticsearch fails with an OutOfMemoryError if this kernel parameter is not set to at least 262144. Docker Desktop on macOS and Windows sets this value automatically, but on Linux hosts it must be set manually: sysctl -w vm.max_map_count=262144.

Magento 2.4.8 supports OpenSearch 2.x as the preferred alternative to Elasticsearch 7. A Docker Magento stack should use OpenSearch, since Elasticsearch 7 is past its EOL date. Configuration in Magento happens through the admin panel under Stores → Configuration → Catalog Search. In the Docker setup, the host is simply the Compose service name (opensearch), and the port is 9200. The healthcheck for OpenSearch checks the cluster health API: a status of green or yellow is acceptable, while red means shards are missing.


# OpenSearch service for Magento 2.4 catalog search
# Elasticsearch has reached EOL, use OpenSearch 2.x instead

  opensearch:
    image: opensearchproject/opensearch:2.13.0
    networks: [magento]
    volumes:
      - opensearch_data:/usr/share/opensearch/data
    environment:
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - "discovery.type=single-node"
      - "plugins.security.disabled=true"   # disable auth for local dev
      - "DISABLE_PERFORMANCE_ANALYZER_AGENT_CLI=true"
    healthcheck:
      test: ["CMD-SHELL", "curl -s -o /dev/null -w '%{http_code}' http://localhost:9200/_cluster/health | grep -qE '200'"]
      interval: 15s
      timeout: 10s
      retries: 10
      start_period: 60s
    restart: unless-stopped
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536

  # OpenSearch Dashboards: optional, activate with profile
  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.13.0
    networks: [magento]
    ports:
      - "5601:5601"
    environment:
      - "OPENSEARCH_HOSTS=http://opensearch:9200"
      - "DISABLE_SECURITY_DASHBOARDS_PLUGIN=true"
    profiles: [monitoring]

7. Node.js for Hyva Tailwind Builds

Hyva themes in a Docker Magento setup need Node.js for the Tailwind CSS build. That means no Node is needed on the host system, but a Node container or a multi-stage build setup is. The simplest solution is a dedicated Node service in docker-compose.override.yml that mounts the Tailwind directory as a volume and runs with a file watcher. Developers can then see CSS changes directly in the browser without manual build steps. The service uses the official Node image in its LTS version.

For production deploys in a Docker Magento stack, the Tailwind build belongs in the CI/CD process: a multi-stage Dockerfile or a separate build container produces the CSS output and places it in the final image. This ensures the production image always contains the current CSS build and that no running Node process is needed in the production container. The Node container in the Compose stack is intended purely for local development and should not be started in CI or production; a profile such as dev-tools encapsulates it cleanly.

8. Volume Strategy for Magento

The volume strategy is one of the most critical aspects of a Docker Magento setup. Magento generates significant amounts of files during operation: compiled code under var/generation, static content under pub/static, logs under var/log and cache under var/cache. These directories should be mapped to named Docker volumes rather than host paths, which noticeably improves I/O performance, especially on macOS with Docker Desktop, where host mounts are notoriously slow.

The source code files themselves are mounted as a host mount for local development, so changes are visible immediately. The pattern for a Docker Magento stack: the root directory as a host mount, the generated and cache directories as named volumes. This approach combines fast iteration cycles for source code with optimal I/O performance for generated files. Production environments have no host mounts at all: every file comes from the image, and only persistent data such as uploads, database data and log files live in volumes.

9. Configuration Options Compared

Building a Docker Magento stack involves several configuration decisions that affect both performance and maintainability.

Decision Option A Option B (recommended) Rationale
PHP-FPM/Nginx A combined container Separate containers Independent scaling, clear health checks
Search engine Elasticsearch 7 (EOL) OpenSearch 2.x Officially supported by Magento 2.4.8
Redis One instance for everything Separate instances Different eviction policies are required
Source code volumes Host mounts only Host mount plus named volumes for cache Noticeably better performance on macOS
Node/Tailwind Installed on the host Docker container with a profile No host dependencies, reproducible

The decision to use separate Redis instances for session and cache looks like overkill at first glance, but it pays off the moment the first production issue occurs: a full page cache instance never evicts sessions when the instances are separated. This prevents unexpected logouts under load, a problem that regularly occurs in production Docker Magento stacks when both roles share a single Redis instance.

10. Summary

A clean Docker Magento stack consists of at least six services with a clear separation of responsibilities: PHP-FPM for application logic, Nginx for static files and proxying, MariaDB for persistent data, two Redis instances for page cache and session, OpenSearch for catalog search, and optionally a Node container for local Tailwind builds. Every service has a healthcheck, and the startup order is controlled through depends_on with condition: service_healthy.

The volume strategy is the most common performance bottleneck in Docker Magento setups: host mounts for source code, named volumes for generated files and cache directories. On macOS, the difference between these two approaches is measurable for Magento requests. Production environments use no host mounts at all: the image contains all application files, and only persistent data lives in volumes. With this configuration, the Magento stack behaves predictably in every environment.

Mironsoft

Magento 2 Docker setup, Hyva development and shop infrastructure

Need a Magento Docker stack for your shop?

We build and configure the complete Docker Magento stack, from PHP-FPM through OpenSearch to the Hyva Tailwind pipeline, with production and local development from a single source.

Stack Setup

Complete Magento Docker stack with PHP-FPM, Nginx, MariaDB, Redis and OpenSearch

Performance Tuning

InnoDB configuration, Redis sizing, OpCache settings and volume strategy

Hyva Integration

Node container for Tailwind builds, CI/CD pipeline and multi-stage Dockerfiles

Docker for Magento: The Essentials at a Glance

Mandatory Services

PHP-FPM, Nginx, MariaDB, Redis (×2), OpenSearch. Every service has its own healthcheck and depends_on: condition: service_healthy.

Redis Strategy

Two instances: page cache (allkeys-lru) and session (noeviction). Prevents unexpected logouts under load.

Volume Performance

Source code as a host mount, generated files (var/, pub/static) as named volumes. Noticeably faster on macOS.

OpenSearch

vm.max_map_count=262144 required on the host. OpenSearch 2.x instead of Elasticsearch 7 (EOL). Single node for dev and staging.

11. FAQ: Docker for Magento

1Why so many services for Magento?
A search engine is mandatory. Redis is strongly recommended. PHP-FPM and Nginx are separated for independent scaling.
2OpenSearch instead of Elasticsearch?
Elasticsearch 7 is EOL. Magento 2.4.8 officially supports OpenSearch 2.x. Always use OpenSearch for new installations.
3OpenSearch fails to start?
vm.max_map_count is too low. On Linux: sysctl -w vm.max_map_count=262144. Docker Desktop sets this automatically.
4Why two Redis instances?
Cache needs allkeys-lru, sessions need noeviction. A single instance can lose sessions under memory pressure.
5Improve volume performance on macOS?
Generated directories (var/, pub/static) as named Docker volumes: noticeably faster than host mounts over FUSE.
6PHP-FPM memory_limit for Magento?
At least 2G for web, 4G for CLI operations (setup:di:compile). As an environment variable or a custom php.ini.
7Node for Hyva Tailwind builds?
Local dev: Node container with a file watcher. Production: multi-stage build in CI. No Node in the production container.
8Control the startup order?
depends_on: condition: service_healthy. Requires correct healthchecks on db, redis and opensearch.
9Sync Nginx and PHP-FPM?
Both mount the same named volume. Nginx read-only, PHP-FPM read-write. Immediately consistent on the same host.
10Which PHP extensions does Magento need?
bcmath, ctype, curl, dom, gd, intl, mbstring, pdo_mysql, soap, xsl, zip. Xdebug only in the dev image.