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.
Table of Contents
- 1. The Magento Stack: Why So Many Services
- 2. Configuring PHP-FPM Correctly
- 3. Nginx as the Magento Frontend Proxy
- 4. MariaDB: Performance and Healthcheck
- 5. Redis: Session and Full Page Cache
- 6. Elasticsearch and OpenSearch: Catalog Search
- 7. Node.js for Hyva Tailwind Builds
- 8. Volume Strategy for Magento
- 9. Configuration Options Compared
- 10. Summary
- 11. FAQ
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?
2OpenSearch instead of Elasticsearch?
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?
allkeys-lru, sessions need noeviction. A single instance can lose sessions under memory pressure.5Improve volume performance on macOS?
var/, pub/static) as named Docker volumes: noticeably faster than host mounts over FUSE.6PHP-FPM memory_limit for Magento?
setup:di:compile). As an environment variable or a custom php.ini.7Node for Hyva Tailwind builds?
8Control the startup order?
depends_on: condition: service_healthy. Requires correct healthchecks on db, redis and opensearch.