New to hosting? Start with the step-by-step install guide.
A production-ready, containerized Valheim dedicated server with automatic updates, backups, log filtering, and comprehensive end-to-end testing. Ships as two artifacts: a stock vanilla image built for uptime, and a BepInEx image with a pinned mod loader and a disaster-response harness for the day a Valheim patch breaks your mods.
- Docker-based deployment - Easy setup with Docker Compose
- Two artifacts, one source -
vanilla(uptime-first) andbepinex(modded, DR-armed); see Image variants - Auto-updates on startup - Server files automatically update on container start/restart
- Automated backups - Scheduled world backups with retention policies
- Disaster response for modded servers - Pre-update snapshots, mod-load verification, hold / rollback / safe mode, nightly canary; see docs/Disaster-Response.md
- Log filtering - Clean, readable logs with noise filtering
- E2E tested - Comprehensive automated test suite, including disaster drills
- Non-root execution - Configurable UID/GID for security
- Systemd support - Native Linux service for non-Docker deployments
- Fully configurable - All settings via environment variables
| Tag | Target | What you get | Pipeline |
|---|---|---|---|
latest, vanilla, 1.2.3, sha-… |
vanilla |
Stock dedicated server. Tracks Steam, auto-updates, no third-party runtime. The DR CLI is present but unarmed. | e2e.yml gates, publish.yml publishes |
bepinex, latest-bepinex, bepinex-5.4.2350, steam-<build>-bepinex, sha-…-bepinex |
bepinex |
Vanilla + BepInExPack_Valheim pinned by version and sha256, plugins persisted under /config/bepinex, DR harness armed (snapshot before updates, mod-load health check, MOD_FAILURE_POLICY). |
bepinex.yml tests, publishes and runs the nightly canary |
The pipelines are independent: a mod-side breakage after a Valheim update can never block a vanilla release. Build locally with docker build --target bepinex . or VALHEIM_VARIANT=bepinex docker compose up -d --build.
Running both servers on one host? Start from deploy/proxmox/docker-compose.yml.
- Docker (20.10+)
- Docker Compose (v2.0+)
- Minimum 4GB RAM, 2 CPU cores, 10GB disk space
Create a new directory for your server and add a docker-compose.yml file:
services:
valheim:
image: abspowergaming/absolute-valheim-server:latest
container_name: valheim-server
environment:
# Server settings (customize these)
- SERVER_NAME=My Valheim Server
- WORLD_NAME=Dedicated
- SERVER_PASS=changeme123
- SERVER_PUBLIC=true
# Update settings
- UPDATE_ON_START=true
# Backup settings
- BACKUPS_ENABLED=true
- BACKUPS_CRON=0 * * * *
# Permissions (match to your host user)
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
ports:
- "2456:2456/udp"
- "2457:2457/udp"
- "2458:2458/udp"
volumes:
- ./data/config:/config
- ./data/server:/opt/valheim/server
stop_grace_period: 2m
restart: unless-stoppedEdit the environment variables in your docker-compose.yml:
SERVER_NAME- Your server's display nameSERVER_PASS- Server password (minimum 5 characters)WORLD_NAME- Name of your world save file
docker compose up -ddocker compose logs -fThe server will:
- Download/update Valheim server files via SteamCMD
- Start the dedicated server
- Connect to Steam for server browser listing
First startup may take 5-15 minutes while downloading server files (~1GB).
- Launch Valheim
- Click Start Game → Start
- Select a character
- Click Join Game tab
- Click Add Server
- Enter:
<your-server-ip>:2456 - Click Connect and enter your password
Ensure these UDP ports are forwarded to your server:
| Port | Purpose | Required |
|---|---|---|
| 2456 | Game traffic | Yes |
| 2457 | Steam queries | Yes |
| 2458 | Crossplay | Only if CROSSPLAY=true |
All configuration is done via environment variables in docker-compose.yml.
| Variable | Default | Description |
|---|---|---|
SERVER_NAME |
My Valheim Server |
Server name in browser |
SERVER_PORT |
2456 |
UDP port (uses +1, +2 also) |
WORLD_NAME |
Dedicated |
World filename |
SERVER_PASS |
(empty) | Password (min 5 chars) |
SERVER_PUBLIC |
true |
Listed in server browser |
CROSSPLAY |
false |
Xbox/MS Store support |
SERVER_ARGS |
(empty) | Additional CLI arguments |
| Variable | Default | Description |
|---|---|---|
UPDATE_ON_START |
true |
Update server on container start |
UPDATE_TIMEOUT |
900 |
Max update time (seconds) |
UPDATE_CRON |
(empty) | Cron schedule for runtime updates |
UPDATE_IF_IDLE |
true |
Only update when no players are connected. The count comes from the server log; if the log cannot be read while the server is up, the update is skipped rather than risked |
STEAMCMD_ARGS |
validate |
Additional SteamCMD args |
| Variable | Default | Description |
|---|---|---|
BACKUPS_ENABLED |
true |
Enable automatic backups |
BACKUPS_CRON |
0 * * * * |
Backup schedule (hourly) |
BACKUPS_DIRECTORY |
/config/backups |
Backup storage path |
BACKUPS_MAX_AGE |
3 |
Days to keep backups |
BACKUPS_MAX_COUNT |
0 |
Max backups (0=unlimited) |
BACKUPS_ZIP |
true |
Compress backups |
BACKUPS_IF_IDLE |
false |
Only back up when no players are connected, counted the same way as UPDATE_IF_IDLE |
| Variable | Default | Description |
|---|---|---|
PUID |
1000 |
User ID for server process |
PGID |
1000 |
Group ID for server process |
PERMISSIONS_UMASK |
022 |
File creation umask |
| Variable | Description |
|---|---|
ADMINLIST_IDS |
Space-separated SteamID64s for admins |
BANNEDLIST_IDS |
Space-separated SteamID64s for bans |
PERMITTEDLIST_IDS |
Space-separated SteamID64s for whitelist |
Find your SteamID64 at steamid.io.
Example:
- ADMINLIST_IDS=76561198012345678 76561198087654321
- BANNEDLIST_IDS=76561198011111111| Variable | Default | Description |
|---|---|---|
TZ |
Etc/UTC |
Container timezone |
LOG_FILTER_EMPTY |
true |
Filter empty log lines |
LOG_FILTER_UTF8 |
true |
Filter invalid UTF-8 |
LOG_FILTER_CONTAINS |
(empty) | Custom filter patterns (pipe-separated) |
Full runbook: docs/Disaster-Response.md. Operator CLI: docker exec <container> /opt/valheim/scripts/valheim-dr status|snapshot|restore|hold|release|safe-mode.
| Variable | vanilla | bepinex | Description |
|---|---|---|---|
DR_SNAPSHOT_BEFORE_UPDATE |
false |
true |
Snapshot server files + world + BepInEx state before a Steam update that changes the build (Steam is queried first; unchanged builds cost nothing) |
DR_KEEP_SNAPSHOTS |
2 |
2 |
Snapshots to keep (~1 GB each, plain tar) |
Plugins go in /config/bepinex/plugins, config in /config/bepinex/config, patchers in /config/bepinex/patchers; they survive image upgrades and restores.
| Variable | Default | Description |
|---|---|---|
BEPINEX_ENABLED |
true |
Inject BepInEx. false runs the modded image as vanilla without rebuilding |
MODCHECK_STRICT |
true |
Container health check fails when mods did not load |
MODCHECK_TIMEOUT |
300 |
Seconds to wait for Chainloader startup complete after each start |
MODCHECK_EXPECT_PLUGINS |
(empty) | Fail when fewer than N plugins load (set to your mod count) |
MODCHECK_FAIL_ON_ANY_ERROR |
false |
Also fail on plugin-level [Error]/[Fatal] lines (default: loader errors only) |
MOD_FAILURE_POLICY |
hold |
On failure: hold (stay up, block updates, go unhealthy), rollback (restore latest snapshot), vanilla (snapshot, then run without mods) |
BEPINEX_VERSION |
(build arg) | Pinned pack version baked into the image; bump with its sha256 in the Dockerfile |
| Container Path | Purpose |
|---|---|
/config |
Persistent data (worlds, backups, admin lists, dr/ snapshots + flags, bepinex/ plugins + config) |
/opt/valheim/server |
Server files (can be cached); on the bepinex image also holds the BepInEx overlay |
-
Locate your local world files:
- Windows:
%USERPROFILE%\AppData\LocalLow\IronGate\Valheim\worlds_local\ - Linux:
~/.config/unity3d/IronGate/Valheim/worlds_local/ - macOS:
~/Library/Application Support/unity.IronGate.Valheim/worlds_local/
- Windows:
-
Copy world files to the server volume:
# Replace "MyWorld" with your actual world name cp MyWorld.db MyWorld.fwl ./data/config/worlds_local/ -
Update
docker-compose.ymlto use your world:- WORLD_NAME=MyWorld -
Restart the server:
docker compose restart
| Extension | Description |
|---|---|
.fwl |
World metadata |
.db |
World data (main save) |
.db.old |
Previous world state |
docker exec valheim-server /opt/valheim/scripts/valheim-backup --force-
Stop the server:
docker compose down
-
Extract backup:
cd data/config unzip backups/valheim_YourWorld_20240101_120000.zip -
Copy world files:
cp valheim_YourWorld_*/YourWorld.* worlds_local/
-
Start the server:
docker compose up -d
# Follow logs
docker compose logs -f
# Last 100 lines
docker compose logs --tail 100docker compose restartdocker compose downdocker compose down
docker compose up -d # Will update on startdocker exec -it valheim-server bash-
Check logs:
docker compose logs --tail 200
-
Verify port availability:
sudo lsof -i :2456 sudo lsof -i :2457
-
Check disk space:
df -h
-
Verify server is running:
docker compose ps
-
Check port forwarding on your router
-
Verify firewall rules:
sudo ufw status
-
Test local connection: Connect using
127.0.0.1:2456from the same machine
-
Check world file permissions:
ls -la data/config/worlds_local/
-
Verify
WORLD_NAMEmatches your.db/.fwlfilenames (without extension)
Increase UPDATE_TIMEOUT in docker-compose.yml:
- UPDATE_TIMEOUT=1800 # 30 minutesValheim servers can use 2-4GB RAM. Set limits in docker-compose.yml:
deploy:
resources:
limits:
memory: 4GIf you want to build the image yourself or contribute to development:
git clone https://github.com/abspwgm/absolute-valheim-server.git
cd absolute-valheim-serverdocker compose up -d --buildThe project includes a comprehensive end-to-end test suite. VALHEIM_VARIANT selects the artifact under test:
# Vanilla artifact (default): shared suite
./tests/run_e2e.sh
# BepInEx artifact: shared suite + mod-load check + disaster drills
VALHEIM_VARIANT=bepinex ./tests/run_e2e.sh
# Run a specific test
./tests/run_e2e.sh server_start
VALHEIM_VARIANT=bepinex ./tests/run_e2e.sh bepinex_disaster_drill- Docker and Docker Compose v2
- Bash shell (Git Bash on Windows) and
jqon the host (DR tests parsevalheim-dr status) - ~2GB free disk space for server files (~6GB for the bepinex suite, which takes snapshots)
- ~4GB RAM for running the container
| Test | Variant | Description |
|---|---|---|
server_start |
both | Verifies container starts and server binary launches |
server_query |
both | Confirms server is listening on UDP ports 2456/2457 |
backup |
both | Tests automatic backup creation and verification |
graceful_shutdown |
both | Validates SIGINT handling and graceful shutdown |
restart_update |
both | Checks server restart and update functionality |
dr_snapshot_restore |
both | Snapshot → tamper → restore → update hold set and honoured → release |
bepinex_loaded |
bepinex | Doorstop env generated from the pack, plugins persisted to /config, chainloader completes, verdict ok, strict health passes |
bepinex_safe_mode |
bepinex | safe-mode on snapshots and restarts without BepInEx (health still passes); off brings mods back |
bepinex_disaster_drill |
bepinex | Corrupt doorstop → server up but verdict not_loaded, health fails, hold set → restore latest → healthy again |
This project uses GitHub Actions with two independent pipelines:
- E2E Tests (e2e.yml) - Lint + vanilla e2e on every push/PR, plus build verification of both targets
- Publish (publish.yml) - Publishes the vanilla image (
latest,vanilla, semver,sha-…) after E2E passes - BepInEx Artifact (bepinex.yml) - Bepinex e2e + disaster drills on every push/PR and nightly as a canary against current Steam; publishes
bepinex/*-bepinextags from its own green runs; opens or updates amod-canaryissue on failure and closes it on recovery
Images are published to:
- Docker Hub:
docker.io/abspowergaming/absolute-valheim-server- what to pull - GitHub Container Registry:
ghcr.io/abspwgm/absolute-valheim-server- private, for testing
Moved 2026-09-22. Both registries used to sit under
fireaimready, a personal account that predates the organization.docker.io/fireaimready/absolute-valheim-serverandghcr.io/fireaimready/absolute-valheim-serverkeep the tags they already have and are no longer updated, so adocker compose pullthere now returns the same image forever. Change the image name toabspowergaming/absolute-valheim-serverand pull again to resume updates.
For bare-metal Linux installations without Docker:
# Ubuntu/Debian
sudo apt update
sudo apt install lib32gcc-s1 lib32stdc++6 libsdl2-2.0-0 steamcmd
# Create valheim user
sudo useradd -m -s /bin/bash valheim# Clone repository
git clone https://github.com/abspwgm/absolute-valheim-server.git
cd absolute-valheim-server
# Copy service file
sudo cp systemd/valheim-server.service /etc/systemd/system/
# Copy and configure environment
sudo cp systemd/valheim.env.example /home/valheim/valheim.env
sudo chown valheim:valheim /home/valheim/valheim.env
sudo nano /home/valheim/valheim.env
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable valheim-server
sudo systemctl start valheim-server# Status
sudo systemctl status valheim-server
# Logs
sudo journalctl -u valheim-server -f
# Restart
sudo systemctl restart valheim-serverThe following features are planned for future releases:
- Mod manager sync - Pull a Thunderstore/r2modman profile into
/config/bepinex/plugins - Web Dashboard - Browser-based server management
- Discord Integration - Player join/leave notifications
- RCON Support - Remote server console
Community contributions are welcome! Please open an issue or pull request.
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Iron Gate AB for creating Valheim
- Valve/Steam for SteamCMD
- BepInEx and denikson / AzumattDev for BepInExPack_Valheim
- The Valheim dedicated server community
Need help? Open an issue on GitHub.