Running Multiple Magento Stores in One Docker Compose Stack
AI generated
FROM
RUN
Docker · Magento · Compose · Multi-Store
Running Multiple Magento Stores in One Docker Compose Stack
Cleanly separating network, database and caches

Anyone running multiple Magento stores for different clients or brands faces the question of whether every shop needs its own Compose stack, or whether one shared stack with clearly separated networks, database schemas and caches is enough. This article shows how a single Docker Compose stack reliably isolates multiple Magento stores without duplicating resources for every shop.

18 min read Docker Compose · Multi-Store · Reverse Proxy Magento 2.4.8 · MySQL · Redis · OpenSearch

1. Why one Compose stack for multiple Magento stores

Agencies and internal teams rarely maintain just a single shop. As soon as a second, third or tenth client joins, the question arises how multiple Magento stores should run in parallel locally and on staging servers. The naive solution of starting a completely separate Compose stack with its own PHP-FPM, its own database, its own Redis and its own OpenSearch for every store still works with two shops. With ten or twenty Magento stores on the same developer machine or the same staging server, this approach hits memory and port limits.

A shared Compose stack for multiple Magento stores solves this problem by bundling base services and only duplicating where real isolation is required. The result is a stack that runs ten shops with the resources that used to be sufficient for three. The key is to clearly distinguish between services that must be isolated per store and those that can safely be shared.

This article walks step by step through the architecture decisions that make up a stable Docker Compose stack for multiple Magento stores: from network topology through database schemas to deployment overrides for CI/CD pipelines.

2. Architecture decision: one codebase or several

Before the Compose stack is built, it must be clear whether the Magento stores share the same codebase with several website and store view configurations, or whether they are completely separate Magento installations for different clients. This is not purely a Docker question, but it heavily determines what the Compose stack looks like. A single codebase with several websites usually also shares one database and one PHP-FPM container in Magento, because the store view separation already happens at the application layer.

With separate client installations, as is common in agency setups, every store instead needs its own PHP-FPM container with its own codebase mount, because Magento versions, modules and customizations differ per client. The Compose stack defines one service block per client store in this case, but uses the same base images and the same shared services for infrastructure components that contain no client-specific logic.

In practice a hybrid approach has proven itself: every Magento store gets its own PHP-FPM container and its own Nginx vhost, but shares MySQL, Redis and OpenSearch as multi-tenant services with logical separation at the schema or index level. This split drastically reduces resource consumption without endangering store isolation.

3. Network and domain routing per store

The most critical part of a Compose stack with multiple Magento stores is routing incoming requests to the correct PHP-FPM container. A reverse proxy like Traefik or nginx-proxy takes on this task by forwarding requests to the matching backend based on the hostname. Every store gets its own local domain, for example shop-a.test, shop-b.test and shop-c.test, declared via labels on the respective service.

Traefik reads these labels automatically from the Docker socket and builds the routing rules from them, without a central configuration file needing manual adjustment for every new store. That is the decisive advantage over a classic Nginx configuration with static server blocks: a new Magento store is simply added as another service with matching labels, and the proxy discovers it automatically on next start.


# docker-compose.yml — multiple Magento stores behind Traefik
services:
  traefik:
    image: traefik:v3.1
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

  magento-shop-a:
    build: ./stores/shop-a
    environment:
      MAGENTO_RUN_CODE: shop_a_store
      DB_HOST: mysql
      DB_NAME: shop_a
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.shop-a.rule=Host(`shop-a.test`)"
      - "traefik.http.services.shop-a.loadbalancer.server.port=8080"
    networks:
      - proxy
      - backend

  magento-shop-b:
    build: ./stores/shop-b
    environment:
      MAGENTO_RUN_CODE: shop_b_store
      DB_HOST: mysql
      DB_NAME: shop_b
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.shop-b.rule=Host(`shop-b.test`)"
      - "traefik.http.services.shop-b.loadbalancer.server.port=8080"
    networks:
      - proxy
      - backend

networks:
  proxy:
  backend:

This structure scales linearly: every additional Magento store is one more service block with its own labels, without ever touching Traefik itself. It is important to use a separate backend network so the PHP-FPM containers can communicate with each other and with MySQL, while only Traefik is directly reachable in the proxy network. That significantly reduces the attack surface compared to a flat network topology.

4. Database strategy: schemas instead of container sprawl

A common beginner mistake with Magento stores in Compose is starting a separate MySQL container for every shop. That wastes memory unnecessarily, because every MySQL instance brings its own buffer pool, its own connection handlers and its own InnoDB structures. The better path is a single MySQL container with its own schema per store. Magento supports this natively via the DB_NAME environment variable per installation, and MySQL itself is designed for dozens of schemas in one instance.

It is important here to create a dedicated database user for every Magento store with privileges limited to its own schema only. A shared root user across all stores would be a security risk, because a compromised store would then also have access to the data of every other shop. Schema separation with granular privileges maintains isolation even with a shared database container.


#!/usr/bin/env bash
# init-multi-store-db.sh — one schema and user per Magento store
set -euo pipefail

STORES=("shop_a" "shop_b" "shop_c")

for store in "${STORES[@]}"; do
  mysql -h mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" <<SQL
CREATE DATABASE IF NOT EXISTS \`${store}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS '${store}_user'@'%' IDENTIFIED BY '${store}_pass';
GRANT ALL PRIVILEGES ON \`${store}\`.* TO '${store}_user'@'%';
SQL
  echo "[OK] Schema and user provisioned for ${store}"
done

mysql -h mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "FLUSH PRIVILEGES;"

5. Shared services: splitting Redis, OpenSearch and Varnish per store

Redis has logical databases indexed 0 through 15, which is ideal for keeping cache and session separate per Magento store without starting an additional container. One store uses Redis DB 0 for cache and DB 1 for sessions, the next store DB 2 and 3, and so on. This separation prevents a cache flush in one store from accidentally taking out the sessions of another store.

With OpenSearch the separation is even simpler, because Magento already uses its own index prefix per store anyway. A single OpenSearch container easily suffices for ten Magento stores, as long as the index names are set uniquely per store via the OPENSEARCH_INDEX_PREFIX configuration. Varnish, on the other hand, needs its own VCL configuration per store, because cache invalidation, backend definition and hostname mapping are store specific, but a single Varnish container can distinguish multiple backends through vcl_recv based on the host.

The basic rule for Magento stores in a shared stack is: services with built in multi-tenant capability (Redis DBs, OpenSearch indices, MySQL schemas) get shared, services without that capability (PHP-FPM because of differing codebases, Varnish VCL because of differing backend logic) get configured dedicated per store or with internal logic.

6. Organizing environment variables cleanly per store

As the number of Magento stores in the same Compose stack grows, managing environment variables quickly becomes confusing if everything lands in a single .env file. The better approach is one .env file per store, included via env_file in the respective service block. This keeps configuration values per shop isolated and keeps diffs for changes to a single store manageable.

Additionally a naming scheme with a store prefix for all store specific variables is recommended, for example SHOP_A_DB_NAME or SHOP_A_REDIS_DB, as soon as values from several .env files converge in an overarching Compose file. That prevents name collisions when two Magento stores happen to expect the same generic variable like DB_NAME in different contexts.

7. Separating media storage and volumes between stores

Every Magento store needs its own volume for pub/media, var/log and var/cache, even if the PHP-FPM containers are built from the same image. A shared volume for multiple stores would cause uploaded product images of one shop to end up in another shop's directory as soon as both containers use the same mount path. Named volumes with a store prefix in the name, for example shop_a_media and shop_b_media, create clear boundaries here.

For backups of multiple Magento stores in one stack, a central backup script that iterates over all store volumes and produces a separate archive per store is worthwhile. That keeps the restore process for a single shop possible in isolation, without having to restore the whole stack from one combined backup. This matters especially in agency setups where individual clients occasionally need a rollback to an older state while the other stores keep running unchanged.

8. Deployment overrides for CI/CD with multiple stores

For production operation of multiple Magento stores from the same Compose setup, a base Compose file with shared definitions and one override per environment that overrides store specific values like domain, resource limits and replica count is recommended. That keeps the basic structure identical for local development, staging and production, while only parameters differ per environment and store.


# docker-compose.prod.yml — production override for multiple Magento stores
services:
  magento-shop-a:
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 1536M
    environment:
      MAGE_MODE: production
      REDIS_CACHE_DB: "0"
      REDIS_SESSION_DB: "1"

  magento-shop-b:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 1024M
    environment:
      MAGE_MODE: production
      REDIS_CACHE_DB: "2"
      REDIS_SESSION_DB: "3"

In the CI/CD pipeline this pattern can be used to deploy only the affected Magento store in a targeted way, instead of restarting the entire stack on every code change. A deployment script that determines the changed store from the Git diff and rebuilds and restarts only its Compose service keeps deployments short and reduces downtime for uninvolved shops to zero.

9. Isolated vs. shared stack patterns compared

The decision about which component to isolate per store and which to share has direct consequences for resource consumption and operational safety. The following overview summarizes the recommended patterns for Magento stores in a shared Compose stack.

Component Isolated per store Shared with separation Recommendation
PHP-FPM Own container Not sensible Isolate, differing codebases
MySQL Only needed for strong isolation One container, one schema per store Share, schema separation suffices
Redis Rarely needed Logical DBs per store Share, DB index separation
OpenSearch Only with very large indices Index prefix per store Share, prefix separation
Varnish Possible but effortful One instance, VCL by host Share with host based VCL
Media volume Own named volume Not sensible Isolate, prevents file collisions

In practice, the biggest savings occur with MySQL, Redis and OpenSearch, because these services are designed for multi-tenant operation anyway. PHP-FPM and media volumes stay isolated instead, because real codebase and file separation is needed there. This combination allows running ten or more Magento stores with the resource footprint that used to require three separate stacks.

Mironsoft

Magento Docker infrastructure and multi store operations

Multiple Magento stores, one clean Compose stack?

We design multi store architectures for agencies and in house teams that bundle resources without sacrificing isolation and security, including network, database separation and deployment automation.

Architecture review

Analyzing existing multi store setups and uncovering resource waste

Compose refactoring

From one stack per shop to a consolidated, secure multi store stack

CI/CD automation

Selective deployments per store with zero downtime for the remaining shops

10. Summary

Running multiple Magento stores in a single Docker Compose stack is not an exotic configuration but the economically sensible default path for agencies and teams with more than one or two shops. The key is sharing services with built in multi-tenant capability like MySQL, Redis and OpenSearch, and isolating only where real codebase or file separation is required, namely PHP-FPM and media volumes.

A reverse proxy with label based routing turns adding new Magento stores into a pure configuration task without manual intervention in central proxy configs. Combined with store specific .env files, schema separation in MySQL and named volumes per store, the result is a stack that runs ten or more shops with the resource footprint that used to require three separate stacks, without endangering the security of individual stores.

Multiple Magento Stores in One Compose Stack — Key Takeaways

Network

Reverse proxy with label routing per store, own backend network per trust zone.

Database

One MySQL container, one schema and one user per store, granular privileges instead of root access.

Caches

Redis DB index and OpenSearch prefix per store, one container for all shops.

Storage

Own named volume per store for media, logs and cache, prevents file collisions.

11. FAQ: Multiple Magento Stores in One Compose Stack

1Separate MySQL container per store needed?
No, one container with a schema and user per store is enough for dozens of stores and saves RAM compared to multiple instances.
2How does Traefik route to the right store?
Via Docker labels with a host rule on the service, read automatically from the Docker socket, no central config file needed.
3Shared Redis instance possible?
Yes, via logical DB indexes per store for cache and session, no additional container needed.
4Why isolate PHP-FPM per store?
Different codebases, versions and customizations per store would mix in a shared container.
5Prevent product image mixing?
Own named volume per store for pub/media, no shared mount path across multiple containers.
6Scaling with twenty stores?
Linear for shared services, limit lies with CPU and RAM of the isolated PHP-FPM containers.
7Deploy only one store, others untouched?
Deployment script determines changed store from Git diff, rebuilds and restarts only that service.
8Own Varnish instance per store?
Usually not necessary, one instance distinguishes backends via vcl_recv based on hostname.
9Organizing environment variables clearly?
Own .env file per store via env_file, plus naming scheme with store prefix for converging variables.
10Biggest mistake in multi store Compose?
Duplicating a complete separate stack per store instead of sharing multi-tenant capable services.