Odin is a modern Rust-powered CLI built for managing Dockerized Valheim dedicated servers with reliability, performance, and simplicity in mind. Designed around type-safe configuration, asynchronous mod downloads via the Thunderstore API, and structured error handling, it delivers a seamless server management experience through a single, dependency-free binary. From server lifecycle automation and BepInEx mod management to cross-platform world synchronization between Windows and Linux using rclone and Tailscale, Odin handles the infrastructure complexity so you can stay focused on the game.
- 🚀 Server Lifecycle — start, stop, restart, update, and manage containers with a single command
- 🧩 Mod Management — classify, download, and install mods from Thunderstore with automatic dependency resolution
- 🌍 World Sync — seamless cross-platform world synchronization between Windows and Linux via SSH/rclone
- 💾 Automated Backups — scheduled world snapshots with restore capabilities and manual backup support
- 🩺 Health Diagnostics — comprehensive system, Docker, and configuration validation before first use
- 📊 Monitoring & Logs — real-time server status, log streaming, and interactive shell access
- 🛠️ DLL Patching — apply and verify assembly patches with toggle control via environment variables
- ⏰ Scheduled Tasks — cron-based automation for updates, restarts, and backups
- 🔌 BepInEx & Valheim+ — full mod loader support with mutually exclusive configuration
- 🎮 Crossplay Support — Xbox and Game Pass crossplay enablement
- 🧪 Type-Safe Config — compile-time validated configuration with sensible defaults
- 📦 Single Binary — zero runtime dependencies beyond Docker and standard Unix tools
- Why Rust?
- Prerequisites
- Installation
- Configuration
- CLI commands
- Mod management
- World sync
- Build & test
- Project structure
- Best practices
- Troubleshooting
Odin leverages Rust's unique strengths to deliver a production-grade server manager that's both reliable and performant:
-
Type safety — compile-time guarantees eliminate entire classes of runtime errors. Odin's configuration system, API responses, and error handling are all type-checked at build time, preventing silent failures in production.
-
Fearless concurrency — async/await with Tokio enables safe, efficient parallel operations. Odin downloads mods concurrently from Thunderstore, monitors server health, and syncs worlds across networks without race conditions or deadlocks.
-
Memory safety without garbage collection — Rust's ownership model guarantees memory safety while keeping Odin lightweight and predictable. No GC pauses means reliable server management even under sustained load.
-
Performance — zero-cost abstractions and minimal overhead. Odin runs as a single, lean binary that starts instantly and uses negligible CPU/memory, ideal for always-on server orchestration.
-
Single binary — no runtime dependencies beyond Docker and standard Unix tools. Deploy Odin anywhere: bare metal, containers, or embedded systems. No Python, Node, or JVM required.
-
Rich ecosystem — Tokio (async runtime), Reqwest (HTTP client), Serde (serialization), Clap (CLI parsing). Odin uses battle-tested libraries that handle complexity so you don't have to.
-
Excellent error handling — Result types and the
?operator make error propagation explicit and ergonomic. Server management demands reliability; Odin's error handling is unambiguous and recoverable. -
Cross-platform compilation — build once, run on Linux, macOS, or Windows. Odin's world sync bridges Windows and Linux seamlessly, powered by Rust's portable standard library.
| Requirement | Minimum | Notes |
|---|---|---|
| Linux kernel | 4.11+ | overlay2 support for Docker |
| CPU cores | 2 (4 recommended) | Valheim idle ≈ 1–2 cores |
| RAM | 4 GB (8 GB recommended) | Valheim idle ≈ 2.8 GB |
| Free disk | 10 GB+ | ~1 GB Docker image + world saves |
| Binary | Purpose |
|---|---|
docker + compose v2 |
Container runtime |
7z |
Mod extraction, world backups |
zip |
Project snapshots |
rclone |
World sync (optional) |
tailscale |
VPN for sync (optional) |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/envcargo install odin
mkdir -p /srv/valheim && cd /srv/valheim
odin init # interactive wizard — fetches all config files from GitHub
odin health # verify environment before first startDownload the pre-built binary for your platform from the Releases page, then:
chmod +x odin-linux-x86_64
mv odin-linux-x86_64 /srv/valheim/odin
cd /srv/valheim
./odin init # interactive wizard — fetches all config files from GitHub
./odin healthgit clone https://github.com/RajaRakoto/odin-vsm.git && cd odin-vsm
cargo build --release
cp target/release/odin /srv/valheim/odin
cd /srv/valheim
./odin init/srv/valheim/
├── docker-compose.yaml ← fetched from GitHub (latest)
├── odin ← binary
├── valheim.env ← generated from valheim.env.example + your answers
├── scripts/ ← fetched from GitHub (apply-patch.sh + any future scripts)
├── config/ ← created automatically on first start
│ ├── backups/
│ ├── worlds_local/
│ └── bepinex/plugins/
├── data/ ← created automatically (steamcmd + Valheim binaries)
└── mods_list.txt ← optional
odinlooks forvalheim.envnext to the binary, falling back to the working directory.
Copy valheim.env.example to valheim.env and edit it.
| Variable | Default | Description |
|---|---|---|
SERVER_NAME |
My Server |
Name in the Steam server browser |
WORLD_NAME |
Dedicated |
World save file name |
SERVER_PASS |
(empty) | Must be ≥ 5 characters |
SERVER_PUBLIC |
false |
List publicly on Steam |
TZ |
Etc/UTC |
Timezone for cron schedules |
| Variable | Description |
|---|---|
UPDATE_CRON |
Auto-pull latest Docker image |
RESTART_CRON |
Auto-restart container |
BACKUPS_CRON |
Auto-backup world saves |
0 4 * * * → Daily at 04:00
0 */6 * * * → Every 6 hours
*/30 * * * * → Every 30 minutes
| Variable | Default | Description |
|---|---|---|
CROSSPLAY |
false |
Xbox/Game Pass crossplay |
SUPERVISOR_HTTP |
false |
Supervisor web UI on port 9001 |
BEPINEX |
false |
BepInEx mod loader |
VALHEIM_PLUS |
false |
Valheim+ (mutually exclusive with BepInEx) |
APPLY_DLL_PATCH |
false |
Enable/disable DLL patching — the hook always runs, the script exits early when false |
PRE_SERVER_RUN_HOOK |
/scripts/apply-patch.sh |
Fixed hook — do not change; controls patch execution via APPLY_DLL_PATCH |
PUID / PGID |
1000 |
UID/GID owning ./data and ./config |
| Variable | Description |
|---|---|
WIN_HOST |
Windows IP or hostname (Tailscale IP recommended) |
WIN_USER |
Windows account name |
WIN_SSH_USER |
SSH login on Windows |
WIN_SSH_PORT |
SSH port (default: 22) |
WIN_SSH_KEY |
Absolute path to SSH private key |
Run ./odin with no arguments to see the full command guide.
odin init # interactive wizard: fetch config from GitHub + generate valheim.envPrompts for SERVER_NAME, WORLD_NAME, SERVER_PASS, TZ (auto-detected), and optional Windows sync config. Fetches docker-compose.yaml, valheim.env.example, and the full scripts/ directory from GitHub at runtime so you always get the latest defaults.
odin health # system, Docker, config, volumes, ports — run before first useodin start # docker compose up -d
odin stop # graceful stop (waits up to 2 min for world save)
odin restart # docker compose restart
odin down # remove container (volumes preserved)
odin update # pull latest image and restartodin status # full server status (passwords hidden)
odin status-password # same, with passwords revealed
odin logs [N] # stream logs (default: 50 lines)
odin shell # interactive shell inside the containerodin backup # manual backup via Supervisor
odin clear-backups # delete all backups in config/backups/ (interactive)
odin restore-worlds # interactively restore a world backup
odin snapshot # archive project to ~/valheim-server.bak.zipodin filter-mods # classify mods via Thunderstore API → mods_list.txt
odin download-mods # download mods to mods_cache/ (no extraction)
odin install-mods # download + install mods to config/bepinex/plugins/
odin clear-mods # stop server, backup worlds, remove mods (interactive)odin sync-worlds --help-guide # setup guide
odin sync-worlds # ⚠ destructive — overwrites server worldsodin apply-patch # apply APPLY_DLL_PATCH change from valheim.env (recreates container)
odin verify-patch # verify the patched DLL is active -- shows MD5 + file sizes
APPLY_DLL_PATCHis the only toggle — set it totrueorfalseinvalheim.env.Important Docker behaviour:
odin restartreuses the existing container environment. A change toAPPLY_DLL_PATCHinvalheim.envis only picked up after the container is recreated (down+start). Runodin apply-patchto do this automatically: it reads the current value fromvalheim.env, confirms with you, then runsdocker compose down+docker compose up -d.Once the fresh container starts,
PRE_SERVER_RUN_HOOK=/scripts/apply-patch.shruns before every Valheim startup and applies or skips the patch based onAPPLY_DLL_PATCH.Requires
./patches/assembly_valheim.dlland./scripts/apply-patch.sh(both mounted read-only indocker-compose.yaml).
odin fix permission # chown 1000:1000 + chmod 755 on ./data and ./configodin uses mods_list.txt as the source of truth.
# Format: Author-ModName-Version (version optional — always fetches latest)
Azumatt-AzuAutoStore-1.2.3
ValheimModding-Jotunn-2.20.0
# SomeAuthor-ClientOnlyMod-1.0.0* ← * → skip entirely
# SomeAuthor-ForceBothMod-1.0.0** ← ** → force classify as "both"
odin filter-mods # Step 1 — classify
odin install-mods # Step 2 — install server-side + both mods
odin start # Step 3 — launchUpdating mods:
odin clear-mods # stop, backup, remove
# edit mods_list.txt
odin install-mods && odin startCopies Valheim save files from Windows to the Linux server via rclone SFTP.
# On Windows: enable OpenSSH Server (Settings → Apps → Optional features)
# On Linux:
ssh-keygen -t ed25519 -f ~/.ssh/valheim_sync
ssh-copy-id -i ~/.ssh/valheim_sync.pub user@windows-ip
# In valheim.env:
WIN_HOST=100.x.x.x # Tailscale IP recommended
WIN_SSH_KEY=/home/youruser/.ssh/valheim_syncodin sync-worlds --help-guide # read first
odin backup && odin sync-worlds # ⚠ destructive
odin startSync aborts if Valheim.exe is running on Windows or players are connected.
cargo build --release # standard build
cargo build --release --target x86_64-unknown-linux-musl # static binary
cargo test # all tests
cargo clippy -- -D warnings # lints
cargo fmt --check # formattingsrc/
├── main.rs — entry point (env load, banner, dispatch)
├── cli.rs — clap CLI (all commands)
├── config.rs — AppConfig (valheim.env → typed struct)
├── error.rs — Error enum + Result alias
├── api/
│ └── thunderstore.rs — Thunderstore REST API client
├── commands/
│ ├── backups.rs — clear-backups
│ ├── docker.rs — start / stop / restart / down / logs / update / shell
│ ├── fix.rs — fix permission
│ ├── health.rs — health diagnostic
│ ├── init.rs — init wizard (fetch config + scripts from GitHub)
│ ├── mods.rs — filter / download / install / clear mods
│ ├── patch.rs — apply-patch / verify-patch
│ ├── status.rs — status / status-password
│ └── worlds.rs — restore-worlds / sync-worlds
└── utils/
├── banner.rs — print_banner() / print_help()
├── display.rs — info / ok / warn / err / confirm
├── fs.rs — sudo_run / sudo_rm_rf / sudo_mkdir_p
└── net.rs — internal_ips() / external_ip()
- Run
odin healthbefore first use — validates the full environment. - Set
SERVER_PASSto ≥ 5 characters or the server won't start. - Match
PUID/PGIDto the user owning./dataand./config(id -u && id -g). - Enable
BACKUPS_CRONfor automatic world snapshots; always backup beforeodin update. - Start with a small mod set (5–10) to validate the pipeline before scaling up.
- Use a Tailscale IP for
WIN_HOST— encrypted, works across networks.
Docker daemon not running
sudo systemctl start docker && sudo systemctl enable dockerPermission errors on startup
odin fix permissionSERVER_PASS too short — must be ≥ 5 characters; update valheim.env then odin start.
ZFS — steamcmd fails with "250 MB required"
zfs set quota=500G <pool/dataset>odin health reports jq missing — not required; all API calls are handled natively.
Mods not loading after install-mods — confirm BEPINEX=true, check config/bepinex/plugins/ is non-empty, then odin restart.
sync-worlds SSH refused — confirm OpenSSH Server is running on Windows and Tailscale is active on both machines.
DLL patch not taking effect after changing APPLY_DLL_PATCH — odin restart does not re-read valheim.env. Run odin apply-patch to recreate the container with the new value. Use odin verify-patch to confirm.
MIT — see LICENSE.