Run a VEIN dedicated server with Docker Compose — with optional automatic updates, HTTP API, and scheduled backups (local rsync or S3 via rclone).
✅ Fast setup (copy/paste compose)
✅ Persistent saves/config via volumes
✅ Backups (local or S3)
✅ Optional Helm chart for Kubernetes
This project is unofficial and not affiliated with the VEIN developers or Valve.
- Quickstart
- Backups
- Kubernetes (Helm)
- Configuration
- FAQ & Troubleshooting
- Related Resources
- Licensing
Below is an example setup using Docker compose and local backups. Local backups will rsync your save and configuration files to the ./backup directory mounted to the sidecar container. See the backups section for more details.
services:
vein:
image: ghcr.io/radical-egg/vein-dedicated-server:latest
container_name: vein-dedicated-server
restart: unless-stopped
ports:
- "27015:27015/udp" # Steam Query Port
- "7777:7777/udp" # Game Port
- "8080:8080/tcp" # HTTP API Port
volumes:
- ./data:/home/vein/server # game data and configs like Game.ini Engine.ini
- ./config:/home/vein/.config/Epic/Vein # Experimental branches store save files here
environment:
TZ: America/Los_Angeles
PUID: 1000 # replace with your users UID
PGID: 1000 # replace with your users GID
VEIN_SERVER_AUTO_UPDATE: true
VEIN_SERVER_NAME: "Vein2Docker"
VEIN_SERVER_DESCRIPTION: '"Vein Dedicated Server using docker"'
VEIN_SERVER_PASSWORD: "secret"
VEIN_SERVER_ENABLE_HTTP_API: true
# VEIN_SERVER_USE_BETA: true
# VEIN_SERVER_ADMIN_STEAM_IDS: "12345,12345,12345562312"
# VEIN_SERVER_SUPER_ADMIN_STEAM_IDS: "12345"
vein-backup-sidecar:
image: ghcr.io/radical-egg/vein-dedicated-backup:latest
container_name: vein-dedicated-backup
volumes:
- ./data:/data/vein-data:ro
- ./config:/data/vein-config:ro
- ./backup:/backup:rw
environment:
PUID: 1000 # replace with your users UID
PGID: 1000 # replace with your users GID
VEIN_SERVER_BACKUP_INTERVAL_SECONDS: 7200
VEIN_SERVER_BACKUP_RETENTION: 10 # set to 0 to keep all backupsYou can use this sidecar container to backup your game saves (Server.vns) and configuration files to a directory mount that you specify in the compose config. It is important to note that the backup container will look for files in /data so I recommend using /data/vein-data and /data/vein-config like the example below.
services:
vein-backup-sidecar:
image: ghcr.io/radical-egg/vein-dedicated-backup:latest
container_name: vein-dedicated-backup
volumes:
- ./data:/data/vein-data:ro
- ./config:/data/vein-config:ro
- ./backup:/backup:rw
environment:
PUID: 1000 # replace with your users UID
PGID: 1000 # replace with your users GID
VEIN_SERVER_BACKUP_INTERVAL_SECONDS: 7200
VEIN_SERVER_BACKUP_RETENTION: 10 # set to 0 to keep all backupsThe backup container has Rclone installed and can be used to send a backup of your Server.vns and Game.ini to and S3 bucket. The example configurations below are what I use for my self hosted Garage S3 instance.
services:
vein-backup-sidecar:
image: ghcr.io/radical-egg/vein-dedicated-backup:latest
container_name: vein-dedicated-backup
volumes:
- ./data:/data/vein-data:ro
- ./config:/data/vein-config:ro
- ./backup:/backup:rw
environment:
PUID: 1000 # replace with your users UID
PGID: 1000 # replace with your users GID
VEIN_SERVER_BACKUP_INTERVAL_SECONDS: 7200
VEIN_SERVER_BACKUP_MODE: s3
VEIN_SERVER_BACKUP_S3_BUCKET: backups
VEIN_SERVER_BACKUP_S3_ENDPOINT: http://garage.localdomain:3900 # Your S3 endpoint url
VEIN_SERVER_BACKUP_S3_PROVIDER: Other # Optional if your provider is not Other
VEIN_SERVER_BACKUP_S3_REGION: garage # Optional, some providers care about this
VEIN_SERVER_BACKUP_S3_KEY_ID: <key id> # required, key id
VEIN_SERVER_BACKUP_S3_ACCESS_KEY: <secret key> # required secret keyhelm repo add radical-egg https://radical-egg.github.io/pineapple-bun/
helm repo update
helm install vein radical-egg/vein-k8s \
--set VEIN_SERVER_NAME="Eggs Strange World" \
--set VEIN_SERVER_DESCRIPTION="nollie 360 flips" \
--set VEIN_SERVER_PASSWORD="secretpass"The developers have some documentation on what configurations are available here.
| Variable | Default | Description |
|---|---|---|
| PUID | 1000 | User ID to run the server as |
| PGID | 1000 | Group ID to run the server as |
| VEIN_SERVER_NAME | "Vein Dedicated Server Docker" | Name of the game server |
| VEIN_SERVER_PASSWORD | "changeme" | Password for game server |
| VEIN_SERVER_DESCRIPTION | "Vein Dedicated server in docker" | Game server description |
| VEIN_SERVER_AUTO_UPDATE | true | Update server on startup |
| VEIN_QUERY_PORT | 27015 | Steam query port (UDP) |
| VEIN_GAME_PORT | 7777 | Game port (UDP) |
| VEIN_SERVER_PUBLIC | true | Specify if the gameserver is public |
| VEIN_SERVER_HEARTBEAT_INTERVAL | "5.0" | Game server heartbeat interval |
| VEIN_SERVER_MAX_PLAYERS | "16" | Max Players for dedicated server |
| VEIN_SERVER_ADMIN_STEAM_IDS | False | A comma delimited list of AdminSteamIDs |
| VEIN_SERVER_SUPER_ADMIN_STEAM_IDS | False | A comma delimited list of SuperAdminSteamIDs |
| VEIN_SERVER_WHITELISTED_PLAYERS | False | a comma delimited list of WhitelistedPlayers |
| VEIN_SERVER_VAC_ENABLED | 0 | Set bVACEnabled in Game.ini |
| VEIN_SERVER_USE_BETA | false | Set true to use -beta argument |
| VEIN_SERVER_BETA_BRANCH | experimental | The default branch to use with -beta arugment |
| VEIN_SERVER_VALIDATE_INSTALL | false | Set to true to provide the validate steamcmd argument on server install/update |
| VEIN_SERVER_HTTP_BIND_ADDRESS | 0.0.0.0 | Set the bind address for the HTTP API listener |
| VEIN_SERVER_HTTPPORT | 8080 | Set the HTTPPort value for Game.ini. Requires VEIN_SERVER_ENABLE_HTTP_API is set to true |
| VEIN_SERVER_ENABLE_HTTP_API | False | Set to true to enable HTTP API on VEIN_SERVER_HTTPPORT. By default this is False |
| VEIN_EXTRA_ARGS | "" | Extra flags passed to the server |
| Variable | Default | Description |
|---|---|---|
| VEIN_SERVER_BACKUP_MODE | rsync | Possible options are rsync or s3 |
| VEIN_SERVER_BACKUP_S3_BUCKET | "" | Name of s3 bucket |
| VEIN_SERVER_BACKUP_S3_ENDPOINT | "" | s3 endpoint for backups |
| VEIN_SERVER_BACKUP_S3_PROVIDER | Other | name of s3 provider (ex. garage, aws, minio) |
| VEIN_SERVER_BACKUP_S3_REGION | garage | s3 region for rclone configurations, some providers want this |
| VEIN_SERVER_BACKUP_S3_KEY_ID | "" | The access key ID for your s3 bucket |
| VEIN_SERVER_BACKUP_S3_ACCESS_KEY | "" | The s3 access key for your s3 bucket |
| VEIN_SERVER_BACKUP_SRC_DIR | /data | The source directory (in the container) of the dedicated server data |
| VEIN_SERVER_BACKUP_DIR | /backup | The source directory (in the container) of the backup directory |
| VEIN_SERVER_BACKUP_RETENTION | 5 | How many backups to keep (e.x if 10 is specified the 10 most recents will be kept and everything else deleted) |
| VEIN_SERVER_BACKUP_INTERVAL_SECONDS | 3600 | How often to run backups (in seconds) |
This is a list of a frequently asked questions and troubleshooting. This list usually will be applicable for docker and non-docker users, so if you are running your server on a Linux or Windows server with steamcmd these solutions generally apply. If they don't, I will be sure to annotate that in the anwsers below.
You may see logs like:
Public...OK
Waiting for client config...OK
Waiting for user info...OK
Error! App '2131400' state is 0x6 after update job.
...This usually means SteamCMD failed to install or update the server files. The most common causes are:
- Networking (container can’t reach Steam over HTTPS)
- Disk space (not enough free space for download + extraction)
- Permissions (SteamCMD can’t write to the install directory)
SteamCMD needs outbound access to Steam endpoints over TCP/443.
Verify HTTPS + DNS from inside the server container:
docker exec -it vein-dedicated-server curl -I https://api.steampowered.comIf this fails, check your DNS settings, firewall rules, proxy, VPN, or restrictive outbound policies.
Downloads can temporarily require extra space during unpacking.
df -hIf you’re using bind mounts / volumes, check the filesystem where your server install actually lives.
Ensure the user inside the container can read/write the install path:
ls -lah /path/to/gameserver
touch /path/to/gameserver/.perm_test && rm /path/to/gameserver/.perm_testIf you’re running rootless or using PUID/PGID, make sure the mounted directories on the host are owned by that UID/GID (or are writable).
SteamCMD tracks install state using an app manifest file at:
/path/to/gameserver/steamapps/appmanifest_*.acf
If the manifest is corrupt or the install state is stuck, you can remove it to force a fresh install on the next update cycle:
rm -f /path/to/gameserver/steamapps/appmanifest_*.acfOn the next start/update, SteamCMD should treat the app as not installed and re-download it.
Note: Only remove the
appmanifest_*.acffile(s). Don’t delete your saved data/config unless you intend to reset everything.
If you’ve confirmed networking/space/permissions and it still fails, the install directory may be in a bad state. The cleanest fix is:
- Deploy to a new empty install path/volume
- Let SteamCMD install fresh
- Restore saves/config from your backups
The gamestate is saved to a file called Server.vns in the SaveGames directory of your gamefiles. The backup “sidecar” container will do an rsync from your gameserver mount -> the backup mount specified in the compose file. The VEIN_SERVER_BACKUP_RETENTION environment variable specifies how many backups to keep. The process of restoring a backup should be relatively straightforward:
-
Stop your server
docker compose down
-
Find your
Server.vnsbackup file and place it in your SavedGames directory. Note the below command will overwrite your current Server.vns file, make sure you back it up if you want to keep itcp /path/to/backup/Server.vns /path/to/Save/SavedGames/Server.vns
-
Start your server back up
docker compose up -d
This warning almost always means the dedicated server can’t be reached from the public internet, so it fails to complete the heartbeat/registration flow.
The most common causes are:
- Missing/incorrect port forwarding on your router (UDP)
- Game port:
7777/udp(default) - Steam query port:
27015/udp(default)
- Game port:
- Firewall/security rules blocking inbound UDP to the host (router firewall, host firewall, VPS security group)
- NAT issues (CGNAT / double NAT) where inbound connections can’t reach your network at all
Make sure you’re forwarding UDP, not TCP, and that the forwarding target is the actual host running the server.
Before you test: ensure the server is running
UDP “port checks” can be misleading if the server isn’t running.
Start the container and confirm it’s listening, then test.
The most reliable test is from a different network (phone hotspot, friend’s network, a cheap VPS, etc.). Testing from inside your own LAN often won’t prove public reachability.
nmap -sU -p 7777 <your.public.ip>
nmap -sU -p 27015 <your.public.ip>Notes:
- UDP scans often show
open|filteredeven when things are fine (because UDP has no handshake). - If you see
closed, something is definitely wrong (port not forwarded, firewall, or server not listening).
echo "hello" | nc -u -w2 <your.public.ip> 7777
echo "hello" | nc -u -w2 <your.public.ip> 27015Notes:
- UDP netcat probes don’t always give a clear success/failure signal.
- If it hangs or times out, treat that as “likely not reachable” and continue debugging.
-
Docker is exposing the ports
- In your compose file, you should have something like:
27015:27015/udp7777:7777/udp
- In your compose file, you should have something like:
-
Router port forward is correct
- External port → internal host IP → same port (UDP)
- The internal host IP should be static/reserved via DHCP
-
Host firewall allows UDP
- Linux
ufw/firewalldrules must allow inbound UDP on both ports
- Linux
-
CGNAT / double NAT
- If your “WAN IP” on your router doesn’t match what websites show as your public IP, you’re likely behind CGNAT.
- In that case, public hosting may be impossible without a real public IP, a VPN/tunnel solution, or hosting on a VPS.
If you see warnings like this and your server shows up in the server list but players can’t join (connection fails/timeouts), a very common cause is CGNAT (Carrier-Grade NAT) from your ISP.
What this means:
Some internet providers don’t give your home network its own true public IP address. Instead, you share one with other customers. When that happens, normal port forwarding often can’t work, even if you set it up correctly in your router.
Why the server list can still work:
Your server can usually register itself with Steam / matchmaking because it makes the first outbound connection. But when a player tries to connect back in, there may be no direct route to your server—so the join fails.
How to confirm:
- Check your router’s “WAN / Internet IP”. If it differs from what sites like “what is my IP” show, you’re likely behind CGNAT.
- Another hint: your router WAN IP is in ranges like
100.64.x.x–100.127.x.x.
Fix options:
- Some ISPs will allow you to purchase a dedicated IP address, this would solve your issue.
- Host the server on a VPS / cloud provider (DigitalOcean, AWS, etc.) where you get a public IP by default.
- Use a tunneling / relay solution (example: Cloudflare Tunnel, Tailscale, ZeroTier). These can work around CGNAT, but setup varies by provider and game.
If you’re unsure, your best bet may be to ask your ISP provider directly if you are behind CGNAT.
Here are some references that may be helpful for configuring VEIN with or without docker.
- VEIN dedicated server setup docs / references (non-Docker): https://vein.wiki.gg/wiki/Vein_Dedicated_Server_Setup
- Developer website for dedicated server setup: https://ramjet.notion.site/dedicated-servers
- VEIN config generator: https://vein-germany.de/config/
This repository and container image include only orchestration code and do NOT distribute any game binaries or assets. Game files are downloaded by the user via SteamCMD under the Steam Subscriber Agreement.
You are responsible for complying with all licenses, terms of service, and EULAs associated with the game.
This project is not affiliated with the game developers or Valve.