A start-to-finish guide for running uberserver on Ubuntu Server 24.04 LTS using Docker and MariaDB.
Repository: https://github.com/ScarylePoo/uberserver
- Ubuntu Server 24.04 LTS
- A non-root user with sudo privileges
- Ports 8200 (TCP) and 8201 (UDP) available
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USERLog out and back in after this, then verify it worked:
docker run hello-worldgit clone https://github.com/ScarylePoo/uberserver.git
cd uberservercp .env.example .env
nano .envFill in your values:
| Setting | Description |
|---|---|
DB_ROOT_PASSWORD |
MariaDB root password. Set something strong. |
DB_PASSWORD |
MariaDB password for the uberserver user. Set something strong. |
DB_NAME |
Database name. Default: uberserver |
DB_USER |
Database username. Default: uberserver |
LOBBY_PORT |
Port clients connect to. Default: 8200 |
NAT_PORT |
Port for NAT hole-punching. Default: 8201 |
MAXMIND_LICENSE_KEY |
Optional. Free key from maxmind.com for country flags. Leave blank to skip. |
EXTRA_ARGS |
Optional extra arguments passed to server.py. |
Never commit your
.envfile to source control — it contains passwords.
docker compose build
docker compose up -dThe build takes a few minutes the first time. Check it started successfully:
docker compose logs -f uberserverYou should see:
MariaDB is up.
Starting uberserver...
Started lobby server!
Press Ctrl+C to stop watching logs. The server keeps running in the background.
sudo ufw allow 8200/tcp
sudo ufw allow 8201/udpIf you're on a cloud VPS (AWS, Hetzner, DigitalOcean etc.), also open these ports in your cloud provider's firewall or security group.
Connect to the database:
docker compose exec db mariadb -u uberserver -p uberserverEnter your DB_PASSWORD when prompted. Then generate a password hash — open another terminal and run:
docker compose exec uberserver /app/venv/bin/python3 -c "
import hashlib, base64
pw = 'your_chosen_password'
print(base64.b64encode(hashlib.md5(pw.encode()).digest()).decode())
"Then back in the MariaDB shell, insert your admin user:
INSERT INTO users (username, password, access, register_date, last_login, last_ip, last_agent, last_sys_id, last_mac_id, ingame_time, bot)
VALUES ('yourusername', 'PASTE_HASH_HERE', 'admin', NOW(), NOW(), '127.0.0.1', '', '', '', 0, 0);Verify it was created:
SELECT id, username, access FROM users;Type exit to leave the MariaDB shell.
Use any Spring lobby client (e.g. SkyLobby or SpringLobby) and add a custom server pointing to your server's IP on port 8200. Make sure TLS/SSL is disabled when connecting to a private server with a self-signed certificate.
| Command | What it does |
|---|---|
docker compose up -d |
Start everything |
docker compose down |
Stop everything |
docker compose restart uberserver |
Restart just the lobby server |
docker compose logs -f uberserver |
Watch live logs (Ctrl+C to stop) |
docker compose ps |
Check container status |
docker compose build --no-cache |
Rebuild from scratch (e.g. after pulling updates) |
To open a bash shell inside the running uberserver container:
docker compose exec uberserver bashTo exit the shell and return to the host:
exitWatch live output from the server (Ctrl+C to stop):
docker compose logs -f uberserverRead the persistent log file inside the container:
docker compose exec uberserver cat /app/server.logTail the last 50 lines of the log file:
docker compose exec uberserver tail -50 /app/server.loggit pull
docker compose build --no-cache
docker compose up -dDocker's restart: unless-stopped policy means containers restart automatically after a reboot, as long as the Docker daemon starts on boot:
sudo systemctl enable dockerOnce logged in as an admin, you manage the server through the ChanServ bot. Commands are prefixed with : and can be sent as a PM to ChanServ, or typed inside a channel (omitting the channel name).
| Command | Who can use it |
|---|---|
:register chanName [founder] |
Moderators |
:unregister chanName |
Moderators |
:op chanName username |
Moderators, channel founder |
:deop chanName username |
Moderators, channel founder |
:history chanName on|off |
Moderators, channel founder |
:antispam chanName on|off |
Moderators, channel founder |
| Command | Who can use it |
|---|---|
:topic chanName topic text |
Ops, moderators, founder |
:kick chanName username |
Ops, moderators, founder |
:mute chanName username 2d reason |
Ops, moderators, founder |
:unmute chanName username |
Ops, moderators, founder |
:ban chanName username 7d reason |
Ops, moderators, founder |
:unban chanName username |
Ops, moderators, founder |
:listbans |
Ops, moderators, founder |
:listmutes |
Ops, moderators, founder |
:setbot username |
Moderators, admins |
:unsetbot username |
Moderators, admins |
Duration format: 1h = one hour, 2d = two days.
:setbotand:unsetbotmust be sent as a PM to ChanServ — they cannot be used inside a channel since they require a username argument.
Every user account has an access level that controls what they can do on the server.
| Level | Description | Inherits from |
|---|---|---|
fresh |
Newly registered, has not accepted the agreement yet | — |
agreement |
Has accepted the agreement, pending email verification | — |
user |
Normal fully verified user | — |
mod |
Moderator | user |
admin |
Administrator | mod, user |
bot |
Bot account with higher flood/bandwidth limits | — |
fresh and agreement are transitional states that users pass through automatically during registration. You should not need to set these manually.
Moderators inherit all user permissions plus moderator-only actions. Admins inherit all moderator and user permissions plus admin-only actions.
Via the database (recommended for fresh, agreement):
docker compose exec db mariadb -u uberserver -p uberserverUPDATE users SET access = 'mod' WHERE username = 'someuser';Via the lobby (admins only, works for user, mod, admin):
Send this command in the lobby server window or as a lobby client admin:
SETACCESS username user|mod|admin
Note: SETACCESS only accepts user, mod, or admin. Use the database directly to set fresh or agreement.
Bot accounts are regular user accounts with a bot flag set. They get significantly higher flood and bandwidth limits, and are shown as bots to connecting clients. The access field should be user — do not set it to bot.
Creating a bot account via the database:
docker compose exec db mariadb -u uberserver -p uberserverINSERT INTO users (username, password, access, register_date, last_login, last_ip, last_agent, last_sys_id, last_mac_id, ingame_time, bot)
VALUES ('botusername', 'HASH_HERE', 'user', NOW(), NOW(), '127.0.0.1', '', '', '', 0, 1);Note the bot = 1 at the end. The access field must be user, not bot.
Managing bot flags via ChanServ (mods and admins):
PM ChanServ or type in a registered channel:
:setbot username
:unsetbot username
:setbot sets bot = 1 on the account. :unsetbot removes the bot flag. Both commands work on online and offline users.
These files live in the root of the repository alongside server.py. After creating or editing any of them, copy them into the running container and restart:
docker compose cp filename.txt uberserver:/app/filename.txt
docker compose restart uberserverDisplayed to every user when they log in. One line per message. Plain text.
Welcome to My Uberserver!
Visit our Discord at discord.gg/example
Shown to new users on registration. They must accept it before their account is activated. One line per paragraph.
Welcome to My Uberserver.
By registering you agree to behave respectfully towards other players.
No cheating, hacking, or abusive behaviour is permitted.
The server administrators reserve the right to ban any user at any time.
If no agreement file is present the server uses a default warning message and does not block registration.
Required if you want email verification on registration and password reset emails. If this file does not exist, email verification is disabled and users can register without providing an email address.
The file has up to 5 lines:
line 1: from address (required)
line 2: SMTP host (required for external relay)
line 3: SMTP port (optional, default 587)
line 4: SMTP username (optional)
line 5: SMTP password (optional)
Example using AuthSMTP:
no-reply@yourdomain.com
mail.authsmtp.com
587
your_authsmtp_username
your_authsmtp_password
Example using Gmail:
no-reply@yourdomain.com
smtp.gmail.com
587
your.email@gmail.com
your_app_password
For Gmail you must use an App Password, not your regular password. Two-factor authentication must be enabled first.
Customises the email sent to users when they register or request a password reset. If this file does not exist, a default SpringRTS-branded email is sent.
The file has 4 header lines followed by the email body template:
line 1: server/community name
line 2: contact URL
line 3: email subject line
line 4: timezone label
line 5+: email body template (can be as many lines as you like)
Example:
Recoil Engine
https://recoilengine.org
Recoil Engine - Email Verification
UTC
You are receiving this email because you recently {reason}.
Your email verification code is: {code}
This code will expire on {expiry_date} at {expiry_time} {tz}.
If you received this message in error, please contact us at {contact}.
Direct replies to this message will be automatically deleted.
Available placeholders for the body template:
| Placeholder | Value |
|---|---|
{name} |
Server/community name (line 1) |
{contact} |
Contact URL (line 2) |
{reason} |
Why the email was sent (e.g. "registered an account on the X lobbyserver") |
{username} |
The username of the registering user |
{code} |
The verification code |
{expiry_date} |
Date the code expires (YYYY-MM-DD) |
{expiry_time} |
Time the code expires (HH:MM) |
{tz} |
Timezone label (line 4) |
Contains a single API key from iphub.info. When present, the server checks each registering user's IP against the IPHub API. Users connecting from VPNs, datacenters, or non-residential IPs will have their account activation delayed by 24 hours.
your_iphub_api_key_here
Get a free API key at https://iphub.info — the free tier allows 1,000 checks per day.
If this file is not present, IP checking is disabled and all registrations are processed immediately.
A list of words to censor in chat. One word per line. The server replaces matched words with *** in channels where censoring is enabled.
You can optionally provide a replacement word by putting it after a space:
badword
anotherbadword replacement
If this file is not present, no word censoring is applied.
A list of domain names or URL fragments to block from chat. One entry per line, lowercase. If a message contains any of these strings it is silently dropped.
badsite.com
anotherbadsite.net
If this file is not present, no URL filtering is applied.
A list of usernames or username fragments that are not allowed to be registered. One entry per line, lowercase.
badusername
admin
moderator
If this file is not present, no username blacklisting is applied beyond the server's built-in character validation.
An alternative to passing arguments via EXTRA_ARGS in .env. Put server startup arguments in this file, one per line.
--no-censor
--min_spring_version 105.1.1
To use it, set in your .env:
EXTRA_ARGS=--loadargs /app/args.txt
Then copy it into the container:
docker compose cp args.txt uberserver:/app/args.txt
docker compose restart uberserverA list of trusted proxy IP addresses, one per line. When a connection comes from a trusted proxy, the server uses the client's real IP instead of the proxy's IP for ban checks, country detection, and rate limiting.
192.168.1.100
10.0.0.1
To enable it, set in your .env:
EXTRA_ARGS=--proxies /app/proxies.txt
Then copy it into the container:
docker compose cp proxies.txt uberserver:/app/proxies.txt
docker compose restart uberserverContainer keeps restarting
docker compose exec uberserver cat /app/server.logCan't connect on port 8200
- Check containers are running:
docker compose ps - Check firewall:
sudo ufw status - Test locally:
telnet localhost 8200
Need to wipe and start fresh (deletes all data)
docker compose down -v
docker compose up -dWipe everything including built images
docker compose down -v
docker rmi $(docker images -q)
docker builder prune -af