connected cleanly in Docker stacks
A Magento Docker stack consists of at least five services: PHP-FPM, Nginx, MySQL, Redis, and OpenSearch. Add RabbitMQ for message queues and Varnish for full page caching. How these services are correctly networked, persisted, and started in the right order determines stability and maintainability.
Table of Contents
- 1. The Service Architecture of a Magento Docker Stack
- 2. MySQL in Docker: Volumes, Init Scripts, and Health Checks
- 3. Redis in Docker: Cache, Sessions, and Persistent Mode
- 4. OpenSearch in Docker: Memory Limits and Indexing
- 5. RabbitMQ in Docker: Virtual Hosts and the Management UI
- 6. Controlling Startup Order with Health Checks
- 7. Network Segmentation in a Multi-Service Stack
- 8. Connection Configuration in Magento's env.php
- 9. Service Configurations Compared
- 10. Summary
- 11. FAQ
1. The Service Architecture of a Magento Docker Stack
A complete Magento Docker stack consists of a set of services that need to work together despite having very different characteristics. MySQL is stateful and needs persistent volumes, warm up time on first start, and health checks before other services connect to it. Redis acts as both cache and session store and can run persistently or ephemerally, depending on configuration. OpenSearch requires specific kernel parameters (vm.max_map_count) and significantly more RAM than the other services. RabbitMQ manages message queues for asynchronous Magento operations such as inventory updates and newsletter dispatch.
The central problem when connecting these services in a Magento Docker stack is startup order. Docker Compose starts all services in parallel by default. PHP-FPM tries to connect to MySQL before MySQL has finished initializing, which causes connection errors on the very first start. Health checks combined with depends_on: condition: service_healthy solve the problem: PHP-FPM only starts once MySQL reports as healthy. This startup sequence needs to be explicitly configured for every service in the stack.
2. MySQL in Docker: Volumes, Init Scripts, and Health Checks
MySQL in a Magento Docker stack absolutely needs a persistent named volume for its database files. Without a volume, all data is lost on every docker compose down. The volume must be properly populated during the very first initialization: MySQL waits for initialization on startup, which can take several seconds. The health check using mysqladmin ping ensures that other services only connect once MySQL is truly ready, not merely once the container has started.
Init scripts for MySQL in a Magento Docker stack live in the /docker-entrypoint-initdb.d/ directory. All SQL and shell files placed there run on the very first start, while the data volume is still empty. That's the right place to create the Magento database, set the character set, and create database users. On a later docker compose down && docker compose up with an existing volume, these init scripts no longer run, which is the correct behavior.
# compose.yaml: MySQL, Redis, OpenSearch, RabbitMQ for Magento stack
services:
mysql:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
MYSQL_DATABASE: magento
MYSQL_USER: magento
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
volumes:
- mysql-data:/var/lib/mysql
- ./env/mysql/conf.d:/etc/mysql/conf.d:ro
- ./env/mysql/init:/docker-entrypoint-initdb.d:ro
networks:
- db
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s # Give MySQL time to initialize on first start
restart: unless-stopped
redis:
image: redis:7.4-alpine
command: >
redis-server
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--save 60 1000
--appendonly no
volumes:
- redis-data:/data
networks:
- db
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
3. Redis in Docker: Cache, Sessions, and Persistent Mode
Redis in a Magento Docker stack takes on two roles: full page cache backend and session storage. Magento can configure Redis for both purposes, but the configuration parameters should differ between the two roles. The cache store can run with an aggressive eviction policy (allkeys-lru) and no persistence at all, since the cache can be rebuilt from the backend at any time. The session store, on the other hand, needs persistence: sessions must survive a container restart, otherwise every logged in customer gets signed out.
The cleanest solution for a Magento Docker stack is two Redis instances: one for cache with --maxmemory-policy allkeys-lru and no RDB persistence, and one for sessions with --maxmemory-policy noeviction and append only file (AOF) persistence enabled. This slightly increases resource requirements but eliminates the risk of cache eviction wiping out a session or session writes degrading cache performance. Both Redis instances are then registered as separate backends in env.php.
4. OpenSearch in Docker: Memory Limits and Indexing
OpenSearch in a Magento Docker stack is the most resource intensive service. It needs at least 1 GB of heap memory for a small Magento installation, and the kernel parameter vm.max_map_count must be set to at least 262144. Without this parameter, OpenSearch won't start. On Linux hosts the parameter is set with sysctl -w vm.max_map_count=262144 and made permanent in /etc/sysctl.conf. On Docker Desktop systems (macOS, Windows), the VM already sets this parameter, which is why OpenSearch starts there even without explicit configuration.
In a Magento Docker stack, OpenSearch should be configured with explicit memory limits: ES_JAVA_OPTS: "-Xms512m -Xmx512m" for the JVM heap size. These two values should be identical, so OpenSearch fully allocates the heap at startup instead of scaling it dynamically. In development mode OpenSearch runs as a single node cluster (discovery.type: single-node), and security features can be disabled for local development. In production, security is mandatory.
# OpenSearch and RabbitMQ service definitions for Magento Docker stack
services:
opensearch:
image: opensearchproject/opensearch:2.18
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
# Disable security for local development only
- DISABLE_SECURITY_PLUGIN=true
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- opensearch-data:/usr/share/opensearch/data
networks:
- db
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health | grep -qv '\"status\":\"red\"'"]
interval: 15s
timeout: 10s
retries: 12
start_period: 60s # OpenSearch needs significant startup time
restart: unless-stopped
rabbitmq:
image: rabbitmq:3.13-management-alpine
environment:
RABBITMQ_DEFAULT_USER: "${RABBITMQ_USER:-magento}"
RABBITMQ_DEFAULT_PASS: "${RABBITMQ_PASSWORD}"
RABBITMQ_DEFAULT_VHOST: "magento"
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- db
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
# Expose management UI only in development
ports:
- "15672:15672" # Management UI, remove in production
5. RabbitMQ in Docker: Virtual Hosts and the Management UI
RabbitMQ in a Magento Docker stack manages message queues for asynchronous Magento processing. Magento uses RabbitMQ for inventory reservations on orders, asynchronous product updates via the Bulk API, and newsletter dispatch. Without RabbitMQ, these operations run synchronously, which causes timeouts under heavy traffic. Like MySQL, RabbitMQ needs a persistent volume so queue messages survive a container restart.
The RabbitMQ configuration for a Magento Docker stack includes a dedicated virtual host for Magento (RABBITMQ_DEFAULT_VHOST=magento), its own credentials, and a persistent data volume. The management UI on port 15672 is a useful tool during development for watching queue depths, unprocessed messages, and consumer status. In production, the management UI port should not be exposed to the host, or it should be protected by an additional authentication layer.
6. Controlling Startup Order with Health Checks
Startup order in a Magento Docker stack is one of the most common sources of errors in new Docker setups. depends_on: mysql only waits until the MySQL container has started, not until MySQL is ready to accept connections. The correct solution is depends_on: condition: service_healthy, which only proceeds once the dependent service's health check reports healthy. That requires every service in the stack to have a health check configured.
In a Magento Docker stack, the optimal startup order looks like this: first start MySQL, Redis, and OpenSearch and wait for them to become healthy. Then start RabbitMQ. Then PHP-FPM, which depends on all the database services. Finally Nginx, which depends on PHP-FPM. The start_period in the health check gives services extra time on their very first start, since OpenSearch needs considerably longer than Redis. This configuration prevents connection errors during stack startup and makes the first docker compose up reliable.
# Startup order using health checks: PHP-FPM waits for all backends
services:
phpfpm:
build:
context: .
target: phpfpm
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
opensearch:
condition: service_healthy
rabbitmq:
condition: service_healthy
networks:
- backend
- db
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:1.27-alpine
depends_on:
phpfpm:
condition: service_healthy
networks:
- frontend
- backend
ports:
- "80:80"
- "443:443"
volumes:
mysql-data:
redis-data:
redis-session-data: # Separate volume for session Redis instance
opensearch-data:
rabbitmq-data:
7. Network Segmentation in a Multi-Service Stack
In a Magento Docker stack with five or more services, network segmentation isn't optional, it's a necessary practice. MySQL, Redis, OpenSearch, and RabbitMQ belong on an internal database network with internal: true. That prevents these services from opening outbound connections to the internet and blocks direct access from the host or the frontend network. PHP-FPM sits on both the backend network (reachable from Nginx) and the database network (reachable from every database service). Nginx sits on the frontend network with a host port and on the backend network for PHP-FPM.
One important exception in a Magento Docker stack: the RabbitMQ management UI, and sometimes MySQL, need direct host port mappings during development so developers can use database tools like TablePlus or the RabbitMQ browser. These ports are only defined in compose.dev.yaml, never in the production compose file. The pattern is simple: base infrastructure without host ports, with a development override that adds the ports that are actually needed.
8. Connection Configuration in Magento's env.php
The connection configuration for every service in a Magento Docker stack lives in app/etc/env.php. All service hostnames match the Compose service names, because Docker's built in DNS resolves those names inside the network. MySQL is referenced as mysql, Redis as redis, OpenSearch as opensearch, RabbitMQ as rabbitmq. No fixed IP addresses, no port forwarding to the host, just direct container to container communication over the internal network.
For a Magento Docker stack in development, it makes sense not to version env.php in the Git repository, but instead to provide an env.php.sample using the Docker service names as a template. A setup script copies the sample file and fills in the credentials from environment variables. That makes onboarding new developers simple: start docker compose up, run the setup script, done. Every service connection is configured correctly and automatically.
9. Service Configurations Compared
Getting the configuration right for each service in a Magento Docker stack has a direct impact on stability and performance. The table below shows the most critical parameters for each service.
| Service | Persistence | Health Check | Critical Parameter |
|---|---|---|---|
| MySQL 8.4 | Mandatory | mysqladmin ping | start_period: 30s |
| Redis (Cache) | Optional | redis-cli ping | allkeys-lru eviction |
| Redis (Sessions) | Mandatory | redis-cli ping | noeviction + AOF |
| OpenSearch 2.x | Recommended | cluster/health API | vm.max_map_count, 1GB heap |
| RabbitMQ 3.13 | Mandatory | rabbitmq-diagnostics ping | Virtual host for Magento |
Every service in a Magento Docker stack needs persistent volumes in production. In development, Redis cache volumes can be skipped, because the cache rebuilds quickly after a restart. MySQL, RabbitMQ, and session Redis volumes must be persistent in every environment. OpenSearch volumes are recommended, because reindexing takes time, especially with large product catalogs.
Mironsoft
Magento Docker stacks, service configuration, and production ready infrastructure
Need a clean Magento Docker stack?
We design and build your complete Magento Docker stack with Redis, RabbitMQ, OpenSearch, and MySQL, including health checks, network segmentation, and persistent volumes.
Stack Design
Complete Compose configuration with all Magento services and the correct startup order
Health Checks
Correct health check configuration for every service with service_healthy depends_on
Production Hardening
Network segmentation, secrets management, and resource limits for production operations
10. Summary
Cleanly connecting a Magento Docker stack with Redis, RabbitMQ, OpenSearch, and MySQL requires three things: persistent volumes for every stateful service, health checks for every database service instance, and correctly configured depends_on: condition: service_healthy for startup order. Network segmentation with internal: true for the database network prevents unnecessary exposure while documenting the allowed communication paths at the same time.
Running Redis as two instances, one for cache and one for sessions, prevents the two roles from interfering with each other. OpenSearch needs vm.max_map_count and enough heap memory. RabbitMQ needs its own virtual host for Magento. MySQL needs a start_period buffer in its health check to allow for initialization. All service hostnames in env.php match the Compose service names, since Docker's built in DNS handles the resolution. The result is a Magento Docker stack that starts reliably on the very first docker compose up and stays stable in operation.
Redis, RabbitMQ, OpenSearch, MySQL in Docker: the essentials at a glance
Startup Order
depends_on: condition: service_healthy instead of depends_on without a condition. Every service needs a health check. start_period gives time for initialization.
Persistent Volumes
MySQL, RabbitMQ, and session Redis absolutely need persistent volumes. Cache Redis is optional. An OpenSearch volume saves reindexing time.
Redis Separation
Two Redis instances: cache with allkeys-lru, sessions with noeviction plus AOF persistence. Prevents cache pressure from evicting sessions.
OpenSearch Requirements
vm.max_map_count=262144 on the host. At least 512 MB heap. discovery.type=single-node for development. start_period: 60s in the health check.