Running a Containerized BZFlag VPS Server (Tutorial)

Need help seting up a server, or have a question on how to run one? This is the place.
Post Reply
Private First Class
Private First Class
Posts: 11
Joined: Fri Feb 09, 2024 9:50 am

Running a Containerized BZFlag VPS Server (Tutorial)

Post by playerStr »

Disclaimer: This guide is licensed under CC0 (
Purchase server infrastructure and run web services at your own risk. I am not liable for credit card charges, server vulnerabilities, etc or any other unforeseen consequences of messing around with web infrastructure. That said, this guide intends to make it easier to avoid such pitfalls.

This post will be updated as I learn more about how to get this setup working well. I'd like to also provide a set of sample configs, so that a good starting template for a server can exist all in one place. Part of publishing a guide like this is to learn, and also to help the community by hammering out bugs in one place, so that we can all benefit :)


The current guide on the forums on how to run a public server viewtopic.php?t=2915 is from 2005, and the details of how you might want to do it have changed a bit. This guide is basically a culmination of my own notes on how to run a server, from playing around with bzfs and a VPS over the past few weeks.

I am assuming basic competency with the command line, and tools like SSH.

From 2005, there are two main changes to the server-running landscape:

1) Cheap Virtual Private Server (VPS) providers are common. You can get a single core instance with 20GB of storage, 1GiB of RAM, and 5TB/month bandwidth for $4 USD, for example. So there is little to no financial barrier to running one or more bzflag servers anymore. VPS instances run in a datacenter and have a good connection to the internet, so that's one less thing to worry about.

2) Containers have hit the scene. Containerizing servers is pretty standard practice now (it could be done in 2005 using, for example, BSD jails, but things are more streamlined now). Containers prevent an errant service running on a machine from blowing up other unrelated stuff on the same machine. That way, if your server gets hacked or something, only that single instance of the server can be affected -- anything else running on the machine should be safe. Containers can also be easily automated.

Before we get into the details, some practical numbers from experiment:

Running a game with 10-15 players only used around 4% of 1 CPU. 2 Hours of play time used around 220MiB TX / 120MiB RX. This means that if you were serving 10-15 players every second for a month, you'd use around 79.2 GiB of bandwidth. That's about 1.6% of our bandwidth allocation. Here, we can see that CPU, rather than bandwidth, is the main bottleneck: saturating 1 CPU would allow us to run ~25 servers each with 10-15 players, and we'd still be using less than half our bandwidth allocation. So there's no need for anything other than the most basic VPS.

Selecting a VPS:

Just find a cheap and simple option. I'd recommend against AWS because it is notoriously expensive and complicated. All you need is a virtual machine with bandwidth, a static IP, and the most basic tools to get it spun up. From there, you can manage everything over SSH. Since VPS offerings change all the time, any recommendation would become stale. Just do a web search, compare options, and find something cheap.

One consideration here is: where do you want the server to be physically located? Server location will affect the amount of lag players from different parts of the world experience, so choose a location that will be a good compromise. You can usually choose location when provisioning a server.

Selecting an OS:

I am assuming linux here, for easy integration with Docker. Specifically, I am using CentOS because it is more familiar for me. Others may be more familiar with Debian, etc. The most important thing is to choose a free OS, so that there won't be an additional monthly charge.

Other notes:

With a VPS, typically, you get charged for what you use. Double check that your configuration will really result in the cheapest bill. Some providers offer a kind of store-credit free trial that will eat up any small mistakes or changes that you have to make in the first month or so (they want people to play with the tools without having to worry about billing), so that may be worth considering when choosing a provider.

Getting started:

New VPS instances typically spawn with SSH enabled and, sometimes, a remote root password set. The very first thing to do is to grab or generate a public/private key pair, copy your public key to the server, and then disable password-based authentication.

If you need to generate a key, just run on your local machine:

ssh-keygen -t rsa -b 4096 -C ""

Choose where to save the key (default might be fine), and make sure to set a password on the key -- otherwise, anyone with your key file can access anything that uses your key pair.

Then run:

ssh-copy-id -i ~/.ssh/<mykey> root@<ip-address>

to copy the public key over to the remote server.

At this point, you can ssh into the remote server using your key. Open /etc/ssh/sshd_config as root, and comment out any line that reads "PasswordAuthentication yes" and write a new line "PasswordAuthentication no".

Restart sshd with (assuming systemd):

sudo systemctl restart ssh

More details here: ... nux-server

Now your server should be at least basically secure.

Configure swap:

Swap can use storage / a file as virtual memory, which prevents running out of memory when a lot of stuff is running. For normal operation of the server, this is probably not important, but when updating the OS and compiling code, you might briefly exceed the memory available on a cheap VPS.

The following creates a 1GiB swapfile and places it under /

As root:

dd if=/dev/zero of=/swapfile count=1024 bs=1MiB
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

Then add the following line to /etc/fstab so it'll persist after a reboot:

/swapfile swap swap sw 0 0 ... n-centos-7

Install packages:

You'll want dev tools to build the server from source, as well as docker. I like to use htop and nload to monitor resource use, so I'm adding that in too. I also use GNU screen. On CentOS it is:

dnf groupinstall "Development Tools"
dnf install ncurses-devel curl-devel c-ares-devel screen htop nload docker

Make a user:

useradd <username>
passwd <username>

then specify the password on the command line.

Switch to the user using:

su -l <username>

Download the containerized source code:

The bzflag devs have created a Dockerfile for bzflag. It is currently on the "feature/container" branch, but will likely be merged into the main branch eventually. Documented at: ... .Container

Read the documentation for info on integrating custom plugins. Basically it's as easy as dumping them under plugins/ and adding an argument to the build step. All plugins that you add will be available to all bzfs containers.

Download it with:

git clone -b feature/container --single-branch --depth 1 bzflag-container-src

Build it with:

cd bzflag-container-src
docker build . -t bzflag-server:latest

This may take a few minutes to build on a single CPU so grab a cup of coffee.

Once the build is complete, you are ready to spawn as many containers as you want.

For each container, we'll need a directory to hold the various bzfs config files and the map. Following the docs, I'll make a directory for each active port:

mkdir -p $HOME/bzfs/5154

Inside of $HOME/bzfs/5154, copy your bzfs.conf, bans.txt, badwords.txt, map.bzw, users.txt, groups.txt, server.users, and any other config files that your plugins might need.

Create the container with:

docker run -d --network slirp4netns:port_handler=slirp4netns --restart unless-stopped -p -p -v $HOME/bzfs/5154:/data --name bzfs5154 bzflag-server:latest -conf bzfs.conf

You only need to do this once. Note that you have to specify the port for both tcp and udp.

Note that this differs from the command given in the bzflag documentation. Setting the network to slirp4netns allows client IPs to filter properly down to the container, so that IP bans and such will work properly. Without it, on a rootless container, IP addresses will get re-written to or similar.

You can view the server log with:

docker logs bzfs5154

Note that you'll need to open the port in your server's firewall to be able to connect to your new server.

You can also set servers to start automatically on boot if you want. The details are in the bzfs docker documentation above.

Opening ports:

On CentOS, this is pretty easy, on other distros the details may vary. Run as root:

firewall-cmd --permanent --zone=public --add-port=5154/tcp
firewall-cmd --permanent --zone=public --add-port=5154/udp

The server will open port 5154 for tcp and udp, and the firewall rule will persist on reboot. The ports might not be opened without a reboot. To open them immediately, run:

firewall-cmd --zone=public --add-port=5154/tcp
firewall-cmd --zone=public --add-port=5154/udp

You'll need to do this for each docker instance / port.

Getting a free domain name:

Go to and claim a domain name for free. You'll get a magic URL. Save this URL somewhere on your server in a text file for safe keeping. Then simply curl the URL to update the domain to point to your server. That's it.

curl<some ...

If your server IP ever changes, just curl the URL again to update the domain.

Getting a list server key:

Go to and login with your forum account. You'll need a key for each domain that you use.
Add the key to your bzfs.conf file with -publickey <your key here> This will allow your server to appear on the public list server.

Getting global groups:

Just like registered users are bzflag forum users, "global" groups are bzflag forum groups. You can request global groups for your servers here: viewtopic.php?t=16016

Other things:

You may want to share things like ban lists, group permissions, etc among multiple servers, so that if a player is banned on one of your servers, they are banned across them all. For read-only files, you can mount a directory as read-only and share that between docker instances. For read-write files like ban lists, you could do this as well, depending on how much isolation you want to have.

You may want to install allejo's VPNBlocker plugin to prevent players from using VPNs to avoid bans:

You probably want to run bzfs with some non-zero debug level (between -d and -dddd) so that more useful stuff is logged.

Another useful plugin is ScoreRestorer that remembers scores for some fixed time, and removes the incentive to rejoin to reset score to 0.


These are the basics of getting a containerized bzfs VPS instance running. You can easily spawn many servers, and each server will run in its own isolated container. Now the challenge shifts towards how to manage them all. Figuring out what to log, how to consolidate logs, etc is a challenge in and of itself. That would be a discussion for another day :)
Private First Class
Private First Class
Posts: 11
Joined: Fri Feb 09, 2024 9:50 am

Re: Running a Containerized BZFlag VPS Server (Tutorial)

Post by playerStr »

Some additional notes from further testing:
Recommended Server Config Setup

Code: Select all

sharedro contains read-only data that is shared between all servers, and is mounted under /sharedro in the container.
shared contains read-write data that is shared between all servers, and is mounted under /shared

Server configs can be segmented into bzfs-common.conf and bzfs-local.conf You can make a shell script to start/restart server instances, and just concatenate the two files to generate bzfs.conf at boot time. For example a

Code: Select all


cat $HOME/bzfs/sharedro/bzfs-common.conf $HOME/bzfs/$1/bzfs-local.conf > $HOME/bzfs/$1/bzfs.conf
docker restart bzfs$1
So ./ 5154 would regenerate the config for the server on port 5154 and restart the container.

An example that creates or updates a docker instance (for example, if you rebuilt the container and want to upgrade):

Code: Select all


docker run --replace -d --network slirp4netns:port_handler=slirp4netns --restart unless-stopped -p$1:$1/tcp -p$1:$1/udp -v $HOME/bzfs/$1:/data -v $HOME/bzfs/sharedro:/sharedro:ro -v $HOME/bzfs/shared:/shared --name bzfs$1 bzflag-server:latest -conf bzfs.conf
Common config example (bzfs-common.conf):

Code: Select all

-publickey <list server key here>
-a 50 38
-adminlagannounce 300
-banfile /shared/bans.txt
-badwords /sharedro/badwords.txt
-groupdb /sharedro/groups.txt
-helpmsg /sharedro/help-rules.txt rules
-jitterdrop 3
-jitterwarn 25
-lagdrop 3
-lagwarn 600
-maxidle 300
-packetlosswarn 5
-password <password here>
-poll vetoTime=60
-poll votePercentage=51
-poll votesRequired=6
-poll voteTime=60
-reportfile report.txt
-spamtime 10
-spamwarn 3
-st 10
-sw 1
-loadplugin autoFlagReset
-loadplugin chathistory
-loadplugin fastmap
-loadplugin logDetail
-loadplugin playHistoryTracker
-loadplugin uselessRampage
-loadplugin bountyHunter
-loadplugin VPNBlocker,/sharedro/VPNBlocker.config.json
-loadplugin ScoreRestorer
-loadplugin serverControl,/sharedro/ServerControl.cfg
-loadplugin retroreflector
Example bzfs-local.conf for a server on port 5155 ($HOME/bzfs/5155/):

Code: Select all

-publictitle "Test instance"
-srvmsg "Welcome to BZ WOW, featuring a new map by playerStr.\nCredit for trees goes to ahs3,\nCredit for fountains goes to heartnet.\nWelcome to the mysterious grove. Who knows what this place was, or what it was used for...\nType /help rules for server rules."
-admsg "BZ WOW: New map: Mysterious Grove"
-mp 50
-mp 20,20,20,20,20,20
-ms 5
-rabbit killer

-p 5155

+f A{5}  #  Agility
+f CL{5} #  Cloaking
+f F{5}  #  rapid Fire
+f G{1}  #  Genocide
+f GM{5} #  Guided Missile
+f IB{10} #  Invisible Bullett
+f L{2}  #  Laser
+f MG{5} #  Machine Gun
+f N{5}  #  Narrow
+f OO{3} #  Oscillation Overthruster
+f QT{5} #  QuickTurn
+f SB{5} #  Super Bullet
+f SE{5} #  SEer
+f SH{10} #  SHield
+f SR{5} #  SteamRoller
+f ST{5} #  STealth
+f SW{3} #  ShockWave
+f T{5}  #  Tiny
+f TH{5} #  THeif
+f V{5}  #  Velocity (high speed)
+f WG{5} #  WinGs

-world "./mystgrove.bzw"
At this point, it's really a matter of how you want to automate things. Do you want to have a set of shell scripts to manage servers? Or maybe offload more of the logic into the container? Do you want to auto-start servers on machine boot, or handle that manually?

The last step is to figure out a way to collect and manage log files. That will be detailed in the next post.
Post Reply