Brazil's biggest MU Online portal — since 2003
Tutorial Advanced Infra

How to orchestrate your MU Online server's services with Docker Compose

Containerize and orchestrate ConnectServer, JoinServer, GameServer, database, and web with Docker Compose, with examples of docker-compose.yml, internal networks, persistent volumes, and production best practices.

RO Rodrigo · Updated on Jul 11, 2020 · ⏱ 17 min read
Quick answer

MU Online servers traditionally run as a collection of loose processes on a Windows machine: ConnectServer, JoinServer, GameServer, database, and sometimes a separate web panel. That works, but it makes consistent backups, zero-downtime updates, and replicating the environment on another machine har

MU Online servers traditionally run as a collection of loose processes on a Windows machine: ConnectServer, JoinServer, GameServer, database, and sometimes a separate web panel. That works, but it makes consistent backups, zero-downtime updates, and replicating the environment on another machine harder. Docker Compose solves this by orchestrating all these services as containers defined in a single declarative file, with isolated internal networks and persistent volumes. This tutorial shows how to structure that orchestration in practice, with configuration examples and specific considerations for a MU server stack.

Why containerize a MU Online server

Containerizing brings three concrete gains: (1) reproducibility — the entire environment (database versions, dependencies) is described in a file, not stored in the memory of whoever originally configured the machine; (2) isolation — a database problem doesn't take down the GameServer, and vice versa; (3) portability — moving the whole server to another machine or provider comes down to copying the configuration files and data volumes.

Overview of the service architecture

ServiceFunctionDepends on
dbDatabase (MSSQL/MySQL) with accounts and characters
connectserverClient entry point, server listdb
joinserverAuthentication and routing to the GameServerdb, connectserver
gameserverMain game logicdb, joinserver
webPanel/site (registration, ranking, shop)db
mu-infra/
├── docker-compose.yml
├── .env
├── db/
│   └── init/            # schema initialization scripts
├── connectserver/
│   └── Data/
├── gameserver/
│   └── Data/
└── web/
    └── (panel code)

Keeping each service in its own folder with a dedicated volume makes both individual backups and isolated component replacement easier.

Example docker-compose.yml

version: "3.9"

services:
  db:
    image: mysql:8.0
    container_name: mu_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: mu_online
    volumes:
      - db_data:/var/lib/mysql
      - ./db/init:/docker-entrypoint-initdb.d
    networks:
      - mu_internal

  connectserver:
    build: ./connectserver
    container_name: mu_connectserver
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "44405:44405"
    networks:
      - mu_internal

  gameserver:
    build: ./gameserver
    container_name: mu_gameserver
    restart: unless-stopped
    depends_on:
      - db
      - connectserver
    ports:
      - "55901:55901"
    volumes:
      - ./gameserver/Data:/app/Data
    networks:
      - mu_internal

  web:
    build: ./web
    container_name: mu_web
    restart: unless-stopped
    depends_on:
      - db
    ports:
      - "8080:80"
    networks:
      - mu_internal

networks:
  mu_internal:
    driver: bridge

volumes:
  db_data:

Environment variables and the .env file

Never leave database passwords or tokens hardcoded in docker-compose.yml. Centralize them in a .env file at the project root:

DB_ROOT_PASSWORD=change-this-password
DB_USER=mu_app
DB_PASSWORD=another-strong-password
GAMESERVER_PORT=55901

Compose automatically reads the .env file in the same folder and substitutes the ${...} variables referenced in the YAML.

Internal networks and port exposure

All services share the mu_internal network, allowing them to communicate with each other by service name (for example, the GameServer reaches the database via the db host, not localhost). Only the ports actually needed by the game client and the web panel should be mapped to the host with ports: — the database must not have a port exposed externally in production, only accessible through Compose's internal network.

Persisting data with volumes

The named volume db_data ensures the database content survives container recreation (docker compose down followed by up). Bind-mount volumes (like ./gameserver/Data:/app/Data), on the other hand, let you edit GameServer configuration files directly from the host without entering the container — essential for quick adjustments to drop rate, experience, and events.

Database backup routine

Add an auxiliary scheduled backup service, or run a command externally via cron, such as:

docker exec mu_db mysqldump -u root -p"$DB_ROOT_PASSWORD" mu_online > backups/mu_online_$(date +%Y%m%d_%H%M).sql

Automate this with a cron job on the host (or a dedicated container with cron running the same command) and maintain a retention policy — for example, daily backups for the last 14 days and weekly ones for the last 3 months.

Updating services without full downtime

To update just the web panel, for example, without affecting the GameServer:

docker compose build web
docker compose up -d --no-deps web

This rebuilds and restarts only the web container, keeping gameserver, connectserver, and db running normally. Database schema changes, however, require careful coordination — always test in a staging environment with a copy of the database before applying it to production.

Basic container monitoring

Essential commands for day-to-day operation:

CommandFunction
docker compose psView status of all services
docker compose logs -f gameserverFollow GameServer logs in real time
docker statsView CPU/memory usage per container
docker compose restart connectserverRestart a specific service
docker compose down && docker compose up -dRecreate the entire stack (data persists via volume)

Common errors and fixes

SymptomLikely causeFix
GameServer can't connect to the databaseWrong hostname (using localhost instead of the service name)Use the service name (db) as the host within the Compose network
Data disappears after recreating containersVolume not declared or removed with -vDeclare named volumes and avoid docker compose down -v unless necessary
Database port accessible externallyUnnecessary port mapping in productionRemove ports: from the db service in production
Password exposed in the repositoryHardcoded credentials in docker-compose.ymlMove everything to .env and add .env to .gitignore
Updating one service takes down the othersUsing a full docker compose down to swap a serviceUse up -d --build <service> to update it in isolation

Docker Compose orchestration checklist

  • Service architecture mapped out (db, connectserver, joinserver, gameserver, web).
  • docker-compose.yml with internal networks and only necessary ports exposed.
  • Sensitive variables centralized in .env and kept out of version control.
  • Named volumes for persistent database data.
  • Automated backup routine tested (restore validated at least once).
  • Isolated per-service update process documented.
  • Basic log and resource-usage monitoring configured.

With the stack orchestrated and reproducible, the natural next step is applying that same infrastructure rigor to the rest of the project — see the tutorial on how to create a MU Online server to review the full configuration of the services you've now containerized.

Frequently asked questions

Do I need to run the whole MU Server in Docker even though it's Windows-based?

It's possible to use Windows containers (Windows Server Core) for emulators compiled for Windows, or run some components through Wine in Linux containers. The most stable approach is usually to containerize the database and web services on Linux, and evaluate the emulator binary case by case.

Does Docker Compose replace an orchestrator like Kubernetes for this use case?

For a small-to-medium MU server, Docker Compose is enough and much simpler to maintain. Kubernetes only makes sense for scenarios with multiple instances, real high availability, or scale requiring automatic scheduling across several hosts.

How do I back up the database running in a container?

Use the database's named volume (e.g., mssql_data or mysql_data) and run periodic dumps with an extra service in the compose file or an external cron job that runs docker exec with the backup command, saving the output outside the container.

Is it safe to expose GameServer ports directly from the container?

Yes, as long as you only map the ports you actually need on the host and keep a firewall configured. Avoid exposing database and administration ports outside the Docker Compose internal network.

How do I update a service without taking down the whole server?

With Docker Compose, you can rebuild and restart a specific service (e.g., docker compose up -d --build web) without affecting the other containers, as long as they don't depend on a shared database schema change.

RO
Founder & editor-in-chief

Rodrigo has run ViciadosMU since the portal's early days. A specialist in MU Online server creation and administration, game history and the evolution of the seasons — he wrote much of the archive before 2024.

Keep reading

Related articles