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.
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
| Service | Function | Depends on |
|---|---|---|
db | Database (MSSQL/MySQL) with accounts and characters | — |
connectserver | Client entry point, server list | db |
joinserver | Authentication and routing to the GameServer | db, connectserver |
gameserver | Main game logic | db, joinserver |
web | Panel/site (registration, ranking, shop) | db |
Recommended directory structure
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:
| Command | Function |
|---|---|
docker compose ps | View status of all services |
docker compose logs -f gameserver | Follow GameServer logs in real time |
docker stats | View CPU/memory usage per container |
docker compose restart connectserver | Restart a specific service |
docker compose down && docker compose up -d | Recreate the entire stack (data persists via volume) |
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| GameServer can't connect to the database | Wrong hostname (using localhost instead of the service name) | Use the service name (db) as the host within the Compose network |
| Data disappears after recreating containers | Volume not declared or removed with -v | Declare named volumes and avoid docker compose down -v unless necessary |
| Database port accessible externally | Unnecessary port mapping in production | Remove ports: from the db service in production |
| Password exposed in the repository | Hardcoded credentials in docker-compose.yml | Move everything to .env and add .env to .gitignore |
| Updating one service takes down the others | Using a full docker compose down to swap a service | Use 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
.envand 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.