How to sync configurations between environments on your MU Online server
Keep configurations consistent between staging and production on your MU Online server, using environment variables, versioned template files, and a clear change promotion process.
A common problem on MU Online servers managed by more than one person is silent configuration drift between environments: the administrator tests a change in staging, it works, but forgets to apply the same change to production — or worse, applies a slightly different version, creating a bug that on
A common problem on MU Online servers managed by more than one person is silent configuration drift between environments: the administrator tests a change in staging, it works, but forgets to apply the same change to production — or worse, applies a slightly different version, creating a bug that only shows up in front of players. Systematically syncing configurations between environments, with a clear process for "what's the same everywhere" versus "what's specific to each environment," eliminates this entire class of error. This tutorial shows how to structure that process, from template files to the change promotion workflow.
The problem of diverging configuration
MU servers typically have dozens of configuration files scattered around: GameServerInfo.dat, ConnectServerInfo.dat, balancing .ini files (experience rate, drop, PvP), event configurations, and database connection strings. When each environment (development, staging, production) has these files edited manually and without control, it's practically guaranteed they will diverge in undocumented ways — and the day this is discovered is usually the day something has already broken in production.
Separating what's common from what's environment-specific
The first conceptual step is to classify each configuration into two categories:
| Category | Example | Where it should live |
|---|---|---|
| Common to all environments | Experience rates, event rules, item options | Git repository, versioned, identical everywhere |
| Specific to each environment | Database IP/host, password, port, license key | Environment variable or secrets vault, never in Git |
Mixing these two categories in the same file is what causes most problems — an event configuration file shouldn't contain the production database password in the same structure.
Configuration template structure
Instead of maintaining a complete configuration file per environment, keep a versioned template with placeholders, and generate the final file at deploy time by substituting the placeholders with the environment's values:
; template: GameServerInfo.template.ini
[Database]
Server={{DB_HOST}}
Database=MuOnline
User={{DB_USER}}
Password={{DB_PASSWORD}}
[Rates]
ExperienceRate=1.0
DropRate=0.7
ZenRate=1.0
[Server]
ServerName={{SERVER_NAME}}
MaxConnections={{MAX_CONNECTIONS}}
Experience and drop rates are the same in staging and production (so the test is representative); the database host and server name change per environment.
Generating the final file per environment
A simple placeholder substitution script, using environment-specific variables:
#!/bin/bash
set -euo pipefail
ENV_FILE=".env.$1" # e.g.: .env.staging or .env.production
source "$ENV_FILE"
envsubst < config/GameServerInfo.template.ini > /opt/mu-server/config/GameServerInfo.ini
echo "[sync] Configuration generated for environment: $1"
Each environment has its own .env file (never committed to Git — only a .env.example with placeholders goes into the repository), keeping sensitive values out of version control while the structure stays identical.
# .env.staging (not versioned)
DB_HOST=10.1.2.10
DB_USER=gameserver_svc_staging
DB_PASSWORD=StagingPassword123
SERVER_NAME=MU-Staging
MAX_CONNECTIONS=50
# .env.production (not versioned)
DB_HOST=10.0.3.10
DB_USER=gameserver_svc
DB_PASSWORD=StrongProductionPassword!2026
SERVER_NAME=MU-Production
MAX_CONNECTIONS=1000
Change promotion flow between environments
Every common configuration change (rates, events, rules) should follow a promotion flow, never be edited directly in production:
- The change is made in the versioned template, on a development branch.
- The template is applied to staging first, generating the final file with the staging variables.
- The team tests the behavior in staging (event, new rate, new item).
- If approved, the change is merged into the main branch.
- The deploy pipeline applies the same template to production, with the production variables.
This flow ensures production never receives a configuration that hasn't been through staging first — eliminating silent drift.
Comparing environments automatically
To detect drift that already exists (files manually edited in the past, outside the process), a structural comparison script helps identify what diverges beyond what's expected:
#!/bin/bash
# Compares the keys present in each environment, ignoring values expected to differ
diff <(grep -oP '^\w+(?==)' /opt/mu-server-staging/config/GameServerInfo.ini | sort) \
<(grep -oP '^\w+(?==)' /opt/mu-server-production/config/GameServerInfo.ini | sort)
If the diff output isn't empty, it means a key exists in one environment and not the other — a clear sign of configuration that has fallen out of sync.
Table of typical configurations and their category
| File/key | Category | Note |
|---|---|---|
| Experience/drop/zen rates | Common | Should be identical between staging and production for representative testing |
| Event rules (time, reward) | Common | Versioned; times may have a specific override in staging for quick testing |
| Database host/port/password | Specific | Never versioned; comes from .env or a secrets vault |
| Server name shown in the client | Specific | Staging should make it clear it's not production |
| Emulator license key | Specific | Each environment with its own license, if applicable |
| Concurrent connection limit | Specific | Staging typically much lower than production |
Syncing the database schema between environments
Beyond configuration files, the database schema (tables, columns, stored procedures) also needs to stay in sync. Use the same set of versioned migrations (see the continuous deployment tutorial) applied to both environments, in the same order, never altering the schema manually directly in production after already having tested it in staging.
Handling sensitive data when syncing staging with production
When staging needs a copy of production data for more realistic testing (not just configuration, but data), never copy the raw dump. Anonymize it first:
-- Run only on the staging database, after restoring a copy
UPDATE Account SET Password = 'generic_test_hash', Email = 'test+' + CAST(AccountID AS VARCHAR) + '@staging.local';
UPDATE MEMB_STAT SET bloc1='0',bloc2='0',bloc3='0',bloc4='0' WHERE 1=1; -- example of zeroing sensitive payment fields, if present
Document this anonymization script as part of the data sync process, not as a manual step "remembered from memory."
Tools that help formalize this process
For larger teams, configuration management tools (Ansible, Terraform for infrastructure, or even a simple templating system with Jinja2/envsubst as shown above) make this process auditable and repeatable. What matters isn't which tool, but the principle: no configuration should exist only in an administrator's head or terminal — everything should be versioned, with a clear process for how it moves from one environment to another.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Bug appears in production but not in staging | Diverging configuration between environments (rate, rule) | Run the structural comparison script and align via the templates |
| Database password leaked in the Git repository | Sensitive value placed directly in the template instead of a variable | Move it to .env/secrets vault and rewrite Git history if necessary |
| Staging doesn't reflect real production behavior | Staging rates/rules different from production "to make testing easier" | Use the same common rates; adjust only what's necessary (e.g. connection limit) |
| Change applied to production without going through staging | No formal promotion process in place | Implement the merge + deploy flow described in this tutorial |
| Test data in staging exposes real player information | Production copy used without anonymization | Run the anonymization script before making the dump available in staging |
Environment sync checklist
- Configurations classified between "common" and "environment-specific".
- Templates versioned in Git, with no embedded secrets.
.envfiles per environment, outside version control.- Promotion flow (staging before production) documented and followed.
- Structural comparison script run periodically to detect drift.
- Database schema synced via versioned migrations, not manual editing.
- Anonymization process defined for data copied from production to staging.
With configurations reliably synced between environments, the next step is connecting this process to the automated deploy pipeline — see the continuous deployment script tutorial for MU Online servers to close the loop between configuration, build, and production release.
Frequently asked questions
What's the difference between syncing and simply copying config files between environments?
Blindly copying drags environment-specific values (like a production database IP) into another, breaking the destination. Syncing means keeping the structure and non-sensitive values identical, while values specific to each environment (hosts, passwords, keys) come from that environment's own variables.
Does staging need to have exactly the same data as production?
No, and generally it shouldn't. Staging should have the same configuration structure and database schema, but with synthetic data or an anonymized copy — never real player passwords or real payment history.
How do I prevent an administrator from forgetting to apply a configuration change to production after testing it in staging?
Treat every configuration change like a code change: versioned in Git, reviewed by someone else (pull request), and applied through a defined deploy process, not manual editing directly on the server. This removes the dependency on human memory.
Where should I store secrets (passwords, API keys) if they can't go into the Git repository?
Use environment variables injected at deploy time, or a dedicated secrets vault (Vault, AWS Secrets Manager, Azure Key Vault). The repository should only contain templates with placeholders, never the actual secret values.
Is it worth having more than two environments (e.g. dev, staging, production)?
It depends on team size. For a small server with one or two administrators, dev and staging can be combined. For larger teams or servers with a high frequency of changes, separating all three reduces the risk of an unstable change reaching production directly.