How to Containerize Your MU Online Server with Docker
Package MySQL, GameServer, ConnectServer, JoinServer, and DataServer into isolated Docker containers, with docker-compose, persistent volumes, and an internal network, making deployment, backup, and migration of your MU Online server easier.
Running a MU Online server directly on the operating system works, but it creates a recurring problem: MySQL, .NET/VC++ redistributable, and emulator binary dependencies end up scattered and hard to reproduce on another machine. Containerizing the server with Docker solves this — each component (dat
Running a MU Online server directly on the operating system works, but it creates a recurring problem: MySQL, .NET/VC++ redistributable, and emulator binary dependencies end up scattered and hard to reproduce on another machine. Containerizing the server with Docker solves this — each component (database, ConnectServer, JoinServer, GameServer, DataServer) runs isolated, with its own dependencies, and the whole environment can be recreated in minutes with a single command. This tutorial shows how to structure a complete docker-compose setup for a MU server, from individual Dockerfiles to data persistence and the internal network between services.
Why containerize a MU Online server
A traditional MU server (based on emulators like MuEmu or IGCN) is made up of several processes that need to run simultaneously and communicate with each other: MySQL, DataServer, JoinServer, ConnectServer, and GameServer. Without containers, migrating this whole set to another machine means manually reinstalling every dependency, reconfiguring paths, and hoping the versions match. With Docker, each piece becomes a versioned image with its dependencies declared in a Dockerfile — the entire environment becomes reproducible, testable on a development machine, and migratable to a new server with docker-compose up.
Prerequisites
- Docker Engine and Docker Compose installed (native Linux, or Docker Desktop with WSL2 on Windows).
- MU emulator binaries (GameServer, ConnectServer, JoinServer, DataServer) already working outside a container, to serve as the base for the Dockerfiles.
- A complete backup of the current database before migrating to the containerized environment.
- Basic knowledge of Docker networking (bridge networks, port exposure).
Overview of the container architecture
| Service | Base image | Exposed ports | Persistent volume |
|---|---|---|---|
mu-mysql | mysql:5.7 or mariadb:10.6 | 3306 | mu-db-data |
mu-dataserver | mcr.microsoft.com/windows/servercore or Wine/Linux | 55757 (internal) | mu-data-files |
mu-joinserver | Windows/Wine | 44405 | — |
mu-connectserver | Windows/Wine | 44405 (client) | — |
mu-gameserver | Windows/Wine | 55901+ | mu-game-logs |
All services share an internal Docker network (mu-network), which lets them communicate by service name instead of a fixed IP — essential for portability between development and production environments.
Structuring the docker-compose.yml
version: "3.8"
services:
mu-mysql:
image: mysql:5.7
container_name: mu-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "strong_password_here"
MYSQL_DATABASE: "MuOnline"
volumes:
- mu-db-data:/var/lib/mysql
- ./sql/init:/docker-entrypoint-initdb.d
networks:
- mu-network
ports:
- "3306:3306"
mu-gameserver:
build: ./gameserver
container_name: mu-gameserver
restart: unless-stopped
depends_on:
- mu-mysql
environment:
DB_HOST: mu-mysql
DB_USER: root
DB_PASS: "strong_password_here"
volumes:
- mu-game-logs:/app/logs
networks:
- mu-network
ports:
- "55901:55901"
networks:
mu-network:
driver: bridge
volumes:
mu-db-data:
mu-game-logs:
mu-data-files:
This file is the central piece: it describes every service, their dependencies (depends_on), environment variables, and the shared network.
Writing the GameServer Dockerfile
FROM mcr.microsoft.com/windows/servercore:ltsc2022
WORKDIR /app
COPY ./bin/GameServer.exe .
COPY ./bin/Data/ ./Data/
COPY ./bin/Config/ ./Config/
EXPOSE 55901
CMD ["GameServer.exe"]
For emulators compiled in C++/Delphi for Windows, this is the simplest route: use a Windows Server Core base image and copy the already-compiled binaries. In Linux environments, many admins choose to run the Windows binaries under Wine inside a Linux container, which reduces resource usage compared to a full Windows image.
Data persistence with volumes
Never leave the database or configuration files inside the container's "ephemeral" filesystem. A docker-compose down without named volumes wipes out all player progress. Always declare named volumes (mu-db-data, mu-game-logs) and, when possible, mount host folders directly for configuration files you edit frequently:
volumes:
- ./config/GameServerInfo.dat:/app/Config/GameServerInfo.dat
This lets you edit the configuration on the host without needing to rebuild the image for every small tweak.
Configuring the internal network between services
Inside the mu-network, each container sees the others by the service name defined in Compose. This means that, in the GameServer's connection string to the database, instead of 127.0.0.1 you use mu-mysql:
[Database]
Host = mu-mysql
Port = 3306
User = root
Password = strong_password_here
This is the most common adjustment when migrating an existing configuration to Docker: any reference to localhost for the database needs to become the corresponding service name.
Backing up and restoring the containerized database
# Backup
docker exec mu-mysql mysqldump -u root -pstrong_password_here MuOnline > backup_$(date +%F).sql
# Restore
docker exec -i mu-mysql mysql -u root -pstrong_password_here MuOnline < backup_2026-07-30.sql
Automate this command with a cron job on the host, writing the .sql files outside the container — that way, even if you destroy and recreate the MySQL container, the backup stays safe on the host.
Logs and container monitoring
Use docker logs -f mu-gameserver to follow the GameServer's behavior in real time, and docker stats to monitor CPU/memory usage of each service. For production, it's worth integrating an observability stack (Prometheus + Grafana) collecting container metrics, especially GameServer CPU usage during peak hours, which tends to be the bottleneck on servers with many simultaneous players.
Updating the server without long downtime
When releasing a new emulator version or a configuration fix, the recommended flow is: rebuild only the changed service's image (docker-compose build mu-gameserver), then recreate only that container (docker-compose up -d --no-deps mu-gameserver), keeping MySQL and the other services running. This reduces downtime to seconds, instead of taking down the whole stack for a single fix.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| GameServer can't connect to the database | Connection string still points to localhost | Switch to the Docker service name (mu-mysql) |
Data disappears after docker-compose down | Volume not declared or removed with -v | Use named volumes and avoid down -v in production |
| Container keeps restarting in a loop | Invalid configuration or dependency not ready | Add depends_on with a healthcheck on MySQL |
| Poor performance on Windows containers | Overhead of a full Windows Server Core image | Consider Wine in a Linux container to reduce resource usage |
| Ports not reachable externally | Missing port mapping in compose | Confirm ports: mapping the host port to the container |
Containerization checklist
- Docker and Docker Compose installed and tested.
- Dockerfiles created for each service (MySQL, GameServer, ConnectServer, JoinServer).
- docker-compose.yml with internal network and named volumes defined.
- Connection strings updated to service names.
- Automated and tested database backup routine.
- Logs and metrics for each container being monitored.
- Zero-downtime update process documented and tested.
With the server running in containers, the natural next step is standardizing this environment as the base for new deployments — even for those setting up the project from scratch. See the MU Online server creation tutorial to align this containerized infrastructure with the emulator fundamentals.
Frequently asked questions
Does Docker on Windows work well for MuServer, which normally runs on Windows Server?
Yes, via Docker Desktop with Windows containers, but it's much less common and heavier than Linux containers. The approach most used in the community is running MySQL in a Linux container and keeping the GameServer/ConnectServer binaries (which depend on Windows) running natively or in a Windows VM, containerizing only the part that supports Linux.
Does containerizing the server make it slower?
The overhead of a Docker container is minimal compared to running directly on the host, typically under a 5% difference. The gain in portability, isolation, and ease of deployment more than makes up for that marginal performance loss for most private MU servers.
How do I back up the database when it's inside a container?
Use a named Docker volume for MySQL's data directory, and run docker exec with mysqldump, pointing the output to a file on the host, outside the container. This ensures the backup survives even if the container is recreated or destroyed.
Do I need to rewrite MuServer's configuration to run in Docker?
No rewriting needed, just adjust connection strings. Instead of pointing the GameServer to localhost for the database, you point it to the service name defined in docker-compose (for example, mu-mysql), since containers communicate by service name within the Compose internal network.
Does Docker Compose replace a real production server?
For production with many simultaneous players, the ideal path is evolving to more robust orchestration (Docker Swarm, Kubernetes) or at least well-configured monitoring and automatic restarts. Docker Compose alone is excellent for development, testing, and small/medium servers, but serious production demands extra layers of resilience.