The Ultimate Guide to Hosting Docker Game Servers on a VPS
Hosting game servers using Docker on a VPS makes the process setup clean, consistent, and easy to manage. In this guide, we use a fresh Ubuntu 24.04 or Ubuntu 22.04 and turn it into a powerful game server host for real servers, including Minecraft, Steam, or Source-based games, Valheim, and Palworld.
We know that configuring a VPS can be complex. If you want a pre-configured VPS, PerLod Hosting offers game-optimized VPS plans with this entire stack pre-configured.
Table of Contents
Requirements for Hosting Game Servers Using Docker on a VPS
We want to set up a few details to prepare our Ubuntu server for hosting game servers. including VPS firewall, Docker networking, and filesystem layout.
Tip: While this guide will walk you through the entire process, if you’d prefer a pre-optimized, game-ready VPS with this exact stack already configured, check out our high-performance VPS Hosting Plans.
Prepare the VPS for Hosting Game Servers
We start by preparing the VPS by installing essential tools, configuring the firewall, and setting up the correct timezone. This setup keeps your VPS secure and ready for Docker and game servers.
Run the system update and upgrade with the following command:
sudo apt update && sudo apt upgrade -y
Install the required tools on your server with the command below:
sudo apt install curl wget htop git jq ca-certificates gnupg ufw -y
Set the correct timezone on your server and verify it with the commands below:
sudo timedatectl set-timezone Asia/Dubai
sudo timedatectl status
To configure UFW firewall rules, we only allow SSH now and configure game ports later:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
Enable UFW firewall and check its status with the commands below:
sudo ufw enable
sudo ufw status
Install Docker Engine and Compose Plugin
At this point, you must install Docker and Docker Compose. Docker will containerize each game server, which keeps them isolated and easy to manage, and Docker Compose allows defining multi-container servers in a single file.
Add Docker’s official repository and install it with the following commands:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo $UBUNTU_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
Add user to Docker group to run Docker commands:
sudo usermod -aG docker $SUDO_USER
Remember to log out and then log in to apply the changes.
Verify your Docker and Docker Compose installation by checking their versions:
docker version
docker compose version
Create Filesystem Layout for Game Servers
Now you must create the directory structure and common variables to organize files and persistent data so servers can be updated and restarted safely.
Create the directories with the following command:
sudo mkdir -p /opt/game-servers/{minecraft,steam-generic,backups}
You can also create an Env file for the default, which is optional, with the command below:
cat >/opt/game-servers/.env <<'EOF'
TZ=Asia/Dubai
PUID=1000
PGID=1000
EOF
The .env simplifies environment variables for all servers.
Note: To get your real user’s UID/GID, you can use the id command and replace it in the above file.
Choose Docker Networking Mode: Bridge vs Host
Networking mode affects latency, port mapping, and server connectivity. Here are the two modes for Docker networking:
- Bridge mode: Default, NATed, requires clear ports, safe for multiple containers.
- Host mode: Uses the VPS network directly, reducing latency for some games.
You can start with bridge mode, which maps only the ports you need, and try host mode if your Steam or Source server has connectivity or latency issues.
Create a shared bridge network for container DNS with the command below:
docker network create games-net || true
This network ensures servers can talk to each other if needed.
Deploy Minecraft Java Game Server
Minecraft is the most popular Java server. To deploy the game server, we use itzg/minecraft-server image for easy setup.
Create a docker-compose.yml in /opt/game-servers/minecraft/ with the following command:
cat >/opt/game-servers/minecraft/docker-compose.yml <<'YAML'
services:
mc:
image: itzg/minecraft-server:latest
container_name: mc
restart: unless-stopped
networks:
- games-net
ports:
- "25565:25565/tcp"
environment:
EULA: "TRUE"
TZ: ${TZ:-UTC}
# Choose one of these types; PAPER is a great default:
TYPE: PAPER
# Version (optional; empty = latest compatible):
VERSION: "1.21.1"
# Memory limits inside JVM:
MEMORY: "4G"
# Optional: enable online mode (true = Mojang auth). For testing on LAN, set to FALSE.
ONLINE_MODE: "TRUE"
# Optional: initial server properties:
MOTD: "Dockerized Minecraft Server"
ENABLE_COMMAND_BLOCK: "true"
volumes:
- ./data:/data
- /etc/localtime:/etc/localtime:ro
# Healthcheck for restart logic (optional)
healthcheck:
test: ["CMD", "mc-monitor", "status", "--host", "localhost"]
interval: 30s
timeout: 10s
retries: 6
networks:
games-net:
external: true
YAML
Open firewall port for it with the command below:
sudo ufw allow 25565/tcp
Navigate to the Minecraft game server directory:
cd /opt/game-servers/minecraft
Export the env file and run the Minecraft game server with the commands below:
set -a; . ../.env 2>/dev/null || true; set +a
docker compose up -d
You can check for logs using this command:
docker compose logs -f
Everything is persisted in /opt/game-servers/minecraft/data.
Deploy Generic Steam or Source Game Server Template
This template uses SteamCMD to install or update a server on container start and then launch it. You can reuse it for CS2, Valheim, ARK, etc. by setting env variables. You will need the following factors:
- APP_ID: Find it on SteamDB or official docs.
- APP_DIR: Folder name under /home/steam/serverfiles.
- START_CMD: The game’s launch line, including ports.
- Ports to expose: TCP and UDP as required by the game.
Create a minimal Dockerfile with a non-root user with the following command:
cat >/opt/game-servers/steam-generic/Dockerfile <<'DOCKER'
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive \
STEAMCMDDIR=/opt/steamcmd \
SERVERDIR=/home/steam/serverfiles \
USER=steam HOME=/home/steam
# Base deps
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates locales tzdata curl wget file bzip2 xz-utils \
lib32gcc-s1 lib32stdc++6 libc6-i386 lib32z1 \
net-tools iproute2 tini \
&& rm -rf /var/lib/apt/lists/*
# Create steam user and dirs
RUN useradd -m -d /home/steam -s /bin/bash steam \
&& mkdir -p ${STEAMCMDDIR} ${SERVERDIR} /opt/scripts \
&& chown -R steam:steam /home/steam ${STEAMCMDDIR} ${SERVERDIR} /opt/scripts
# Install SteamCMD
USER steam
WORKDIR ${STEAMCMDDIR}
RUN set -eux; \
curl -L -o steamcmd_linux.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz; \
tar -xvzf steamcmd_linux.tar.gz; rm steamcmd_linux.tar.gz
# Copy start script
USER root
COPY start.sh /opt/scripts/start.sh
RUN chmod +x /opt/scripts/start.sh && chown steam:steam /opt/scripts/start.sh
USER steam
WORKDIR /home/steam
ENTRYPOINT ["/usr/bin/tini","--"]
CMD ["/opt/scripts/start.sh"]
DOCKER
Then, create a start.sh file, which installs and updates the server and then launches it:
cat >/opt/game-servers/steam-generic/start.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
: "${APP_ID:?APP_ID not set}"
: "${APP_DIR:=serverfiles}"
: "${VALIDATE:=false}"
: "${ADDITIONAL_STEAMCMD_ARGS:=}"
: "${START_CMD:?START_CMD not set}"
STEAMCMDDIR="${STEAMCMDDIR:-/opt/steamcmd}"
SERVERDIR="${SERVERDIR:-/home/steam/serverfiles}"
echo "[INFO] Running SteamCMD update/install for app ${APP_ID} (validate=${VALIDATE})"
VALIDATE_FLAG=""
if [ "${VALIDATE}" = "true" ]; then
VALIDATE_FLAG="validate"
fi
"${STEAMCMDDIR}/steamcmd.sh" +@sSteamCmdForcePlatformType linux \
+login anonymous \
+force_install_dir "${SERVERDIR}" \
+app_update "${APP_ID}" ${VALIDATE_FLAG} ${ADDITIONAL_STEAMCMD_ARGS} \
+quit
echo "[INFO] Launching: ${START_CMD}"
cd "${SERVERDIR}"
exec bash -lc "${START_CMD}"
BASH
Now set up a docker-compose.yml with correct APP_ID, START_CMD, and ports with the command below:
cat >/opt/game-servers/steam-generic/docker-compose.yml <<'YAML'
services:
steam-server:
build: .
container_name: steam-server
restart: unless-stopped
networks:
- games-net
environment:
TZ: ${TZ:-UTC}
# --- COMMON ENV ---
APP_ID: "730" # EXAMPLE: replace with your game's dedicated server AppID
APP_DIR: "serverfiles"
VALIDATE: "false" # set "true" occasionally
ADDITIONAL_STEAMCMD_ARGS: "" # e.g. "-beta public -betapassword yourpass"
# START_CMD MUST be adapted to your title:
# Example (Source-style — replace map, gamemode, etc.):
START_CMD: "./game/bin/server_binary -dedicated +map de_inferno -tickrate 128 -gameport 27015 -ip 0.0.0.0"
volumes:
- ./data:/home/steam/serverfiles
# Replace these with the actual required ports for your game:
ports:
- "27015:27015/udp"
- "27015:27015/tcp"
- "27020:27020/udp"
networks:
games-net:
external: true
YAML
Adjust the firewall rules with the following commands:
sudo ufw allow 27015/tcp
sudo ufw allow 27015/udp
sudo ufw allow 27020/udp
Start the server with the commands below:
cd /opt/game-servers/steam-generic
set -a; . ../.env 2>/dev/null || true; set +a
docker compose up -d --build
docker compose logs -f
Note: For host networking for lower latency and fewer NAT quirks, you can change the compose service:
network_mode: "host"
ports: [] # remove ports section when using host mode
Then, remove the external network under that service. With host mode, open the exact ports on the VPS firewall because there’s no Docker NAT. To do this, you can run:
sudo ufw allow 27015/tcp
sudo ufw allow 27015/udp
sudo ufw allow 27020/udp
Deploy Palworld or Valheim Variant
You can use the same Steam template for different games by changing ports, START_CMD, and APP_ID.
# Edit docker-compose.yml START_CMD and ports for Palworld:
# START_CMD example (adjust path/binary per server files layout):
# START_CMD: "./Pal/Binaries/Linux/PalServer-Linux-Shipping PalWorldSettings.ini -useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS -publicip=0.0.0.0 -port=8211 -QUERYPORT=27015"
# Ports:
# - "8211:8211/udp"
# - "27015:27015/udp"
Open required firewall ports and start the server:
sudo ufw allow 8211/udp
sudo ufw allow 27015/udp
docker compose up -d --build
For Valheim, typical ports 2456–2458/udp:
# START_CMD: "./valheim_server.x86_64 -name \"MyDockerValheim\" -port 2456 -world \"DockerWorld\" -password \"S3cret\" -public 1"
# Ports:
# - "2456:2456/udp"
# - "2457:2457/udp"
# - "2458:2458/udp"
sudo ufw allow 2456:2458/udp
docker compose up -d --build
Backup Game Servers in VPS
You must consider protecting game data and keeping servers updated. To create backups, you can use the simple tar command.
For Minecraft:
cd /opt/game-servers/minecraft
sudo tar czf "/opt/game-servers/backups/mc-$(date +%F-%H%M).tar.gz" data
For Steam generic:
cd /opt/game-servers/steam-generic
sudo tar czf "/opt/game-servers/backups/steam-$(date +%F-%H%M).tar.gz" data
You can automate the backups with a cron job, for example, take backups daily at 4:
crontab -e
# Add lines (adjust paths):
0 4 * * * tar czf /opt/game-servers/backups/mc-$(date +\%F-\%H\%M).tar.gz -C /opt/game-servers/minecraft data
15 4 * * * tar czf /opt/game-servers/backups/steam-$(date +\%F-\%H\%M).tar.gz -C /opt/game-servers/steam-generic data
To keep your Minecraft game server updated, you can pull and recreate the image with the following commands:
cd /opt/game-servers/minecraft
docker compose pull && docker compose up -d
For the Steam server, the start.sh runs app_update every start. If the Dockerfile has changed, rebuild and recreate it:
cd /opt/game-servers/steam-generic
docker compose build --no-cache && docker compose up -d
Docker keeps container logs at /var/lib/docker/containers/…/json.log. You can avoid huge log files with the following config under each service:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "3"
Resource Limits and Stability in Game Servers
You can prevent a single game server from CPU or RAM high usage by adding the following config:
deploy:
resources:
limits:
cpus: '2.0'
memory: 6g
Note: deploy is used by Swarm; for Compose alone, some runtimes still apply cgroup limits. Alternatively, you can use:
mem_limit: 6g
cpus: 2.0
Compose v2 still accepts these service-level keys for cgroup constraints.
FAQs
Why should I use Docker for game servers instead of installing them directly on the VPS?
Docker provides isolation, ensuring that each game server runs in its own contained environment. This prevents conflicts between dependencies, makes updates and rollbacks easier, and simplifies management.
What’s the difference between Bridge and Host Docker networking mode?
Bridge Mode (Default): Docker creates a private internal network for containers, and you must manually map ports. This is more secure and ideal for running multiple game servers on one VPS.
Host Mode: The container shares the VPS’s network stack directly, eliminating the NAT layer. This can reduce latency and solve connectivity issues for some Steam/Source games, but it is less isolated.
It is recommended to start with Bridge mode and switch to Host only if you experience problems.
How do I find the correct APP_ID for a Steam game server?
The best resource is the official SteamDB site. Search for your game and look for the “App ID” of the dedicated server tool, not the client game.
Conclusion
By following this guide, you have successfully transformed a basic Ubuntu VPS into a powerful, organized, and secure platform for hosting multiple game servers using Docker.
We hope you enjoy Hosting Game Servers Using Docker on a VPS guide. Subscribe to our X and Facebook channels to get the latest articles.
For further reading: