The easiest way to run Valheim in Docker is the community image lloesche/valheim-server (published on GitHub's registry as ghcr.io/lloesche/valheim-server). It downloads the server with SteamCMD on first start, runs it under a supervisor that also checks for updates every fifteen minutes and takes an hourly zipped backup, and is configured entirely through environment variables: SERVER_NAME, WORLD_NAME, SERVER_PASS, plus SERVER_ARGS for anything else such as -crossplay. You mount two volumes - /config for worlds, backups and list files, /opt/valheim for the server install - map UDP 2456-2457, give it a stop grace period of a couple of minutes, and it behaves. The defaults are sensible for a vanilla server; the two you should change on a modded one are the update schedule and, if you care about disk space, backup retention.
This post assumes Docker and Compose already work on the machine. If not, Docker on a VDS gets you there. For the non-container install, see Valheim on Linux with SteamCMD.
Why run Valheim in a container at all#
A native install with a systemd unit is perfectly good, so the container needs to earn its place. What it gives you:
- Nothing installed on the host except Docker. No
i386architecture, no SteamCMD package, no libraries for crossplay. Rebuilding the machine is "install Docker, restore two folders,docker compose up -d". - Updates, restarts and backups already scheduled, with an idle check so none of them interrupts players. On a native install you would write those jobs yourself.
- Clean separation when the same machine runs other things - a Discord bot, a second game server, a website. Each lives in its own container with its own ports and files.
What it costs: one more layer to debug, logs that come through the supervisor rather than straight from the server, and a dependency on a community-maintained image. The image is widely used and actively maintained, but it is not an Iron Gate product; read its README when something changes, because the README is the authority on its variables, not this post. Variable names and defaults below are the ones the README documents at the time of writing.
The compose file#
A docker-compose.yml with the settings that matter:
services: valheim: image: ghcr.io/lloesche/valheim-server container_name: valheim cap_add: - sys_nice volumes: - ./config:/config - ./data:/opt/valheim ports: - "2456-2457:2456-2457/udp" env_file: - ./valheim.env restart: unless-stopped stop_grace_period: 2mSERVER_NAME=Longship CrewWORLD_NAME=MidgardSERVER_PASS=herring-barrel-42SERVER_PUBLIC=trueTZ=Europe/Berlin$ mkdir -p ~/valheim && cd ~/valheim$ docker compose up -d$ docker compose logs -fThe first start takes several minutes: the container runs SteamCMD, downloads the server into ./data, then starts it and generates the world. Wait for the same log lines a native server prints - Load world: Midgard and Game server connected - before trying to join.
Three lines in the compose file deserve a note:
stop_grace_period: 2mgives the server time to save. Ondocker compose downordocker stop, Docker sends a stop signal, the supervisor passes a clean shutdown to Valheim, and the world is written. Docker's default grace period is 10 seconds, after which it sendsSIGKILL- and a big world that has not finished saving loses everything since the last save. This is the single most important line in the file. Withdocker runthe equivalent is--stop-timeout 120.cap_add: sys_nicelets the server raise its own process priority, which the README recommends. It is harmless if you leave it out; you lose a small scheduling advantage under load.restart: unless-stoppedbrings the container back after a reboot or a crash, but not after you stopped it on purpose.
The env file keeps the password out of the compose file, so you can share or commit the compose file without leaking it. Keep valheim.env readable only by you.
Environment variables that matter#
The image has dozens of variables. These are the ones most servers set or should know about:
| Variable | Default | What it does |
|---|---|---|
SERVER_NAME | My Server | Name in the server list |
SERVER_PORT | 2456 | Game port; the server also uses the next one up |
WORLD_NAME | Dedicated | World file to load or create |
SERVER_PASS | secret | Join password, minimum 5 characters |
SERVER_PUBLIC | true | List in the community browser |
SERVER_ARGS | empty | Extra launch arguments, passed through as-is |
ADMINLIST_IDS | empty | Space-separated IDs written to adminlist.txt |
BANNEDLIST_IDS | empty | IDs written to bannedlist.txt |
PERMITTEDLIST_IDS | empty | IDs written to permittedlist.txt |
TZ | Etc/UTC | Time zone the cron schedules run in |
PUID / PGID | 0 | User and group the files are owned by |
SERVER_ARGS is where everything without its own variable goes - crossplay, world modifiers, a shorter save interval:
SERVER_ARGS=-crossplay -saveinterval 900 -preset casual -modifier portals casualDo not repeat -name, -world, -password or -port in SERVER_ARGS; the image already builds those from the dedicated variables, and duplicates produce confusing results. Every argument and its gotchas are in the Valheim launch arguments reference.
The list variables are convenient but have a sharp edge: on start the image writes them into the list files, so an ID you add by hand to adminlist.txt and forget to put in ADMINLIST_IDS may not survive the next restart. Pick one method and stick to it. The ID formats, including the platform prefixes crossplay needs, are in the Valheim list files guide.
PUID and PGID decide who owns the files in the volumes. At the default of 0 they belong to root on the host, which makes editing them over SFTP as a normal user awkward. Set both to your own user's IDs (id -u and id -g) before the first start if you will be editing files on the host.
Volumes: where the world actually lives#
| Container path | Host folder above | Contents |
|---|---|---|
/config | ./config | worlds_local/, backups/, adminlist.txt and friends, BepInEx config |
/opt/valheim | ./data | The server install and the BepInEx install |
/config is the one to protect. It is the equivalent of the save directory on a native install: worlds in /config/worlds_local, the image's backups in /config/backups, the three list files at the top level. /opt/valheim can be thrown away; if it is empty, the container downloads the server again on the next start. Mounting it is still worth doing, so a container rebuild does not re-download a gigabyte or two every time.
To move an existing world in, stop the container, copy Midgard.db and Midgard.fwl into ./config/worlds_local/, set WORLD_NAME=Midgard - matching capitals exactly - and start it. If the log says it is generating a world rather than loading one, the name does not match. The full procedure, including worlds from Steam Cloud, is in uploading a local Valheim world.
Use bind mounts (folders on the host) rather than named volumes for /config. You can see the files, copy them with ordinary tools, and back them up without going through Docker.
Updates and restarts on a schedule#
The image runs an update check on a cron schedule inside the container:
| Variable | Default | Meaning |
|---|---|---|
UPDATE_CRON | */15 * * * * | Check SteamCMD for a new server build every 15 minutes |
UPDATE_IF_IDLE | true | Only apply an update when no players are connected |
RESTART_CRON | 0 5 * * * | Daily restart at 05:00 in TZ |
RESTART_IF_IDLE | true | Skip the restart if players are on |
STEAMCMD_ARGS | validate | Extra arguments for the SteamCMD update |
When the check finds a new build and nobody is playing, the image stops the server cleanly, updates it and starts it again. For a vanilla group this is exactly right: after a Valheim patch the server is usually current before anyone has finished updating their own game.
For a modded server it is exactly wrong. A Valheim patch breaks BepInEx mods until their authors update them, and an image that updates itself the moment the server is empty will hand you a broken server the morning after patch day. Turn it off by setting UPDATE_CRON to an empty value, and update deliberately when your mods are ready: back up, then docker compose restart - the image checks for updates on start - and test. Updating a Valheim server covers the timing.
The daily restart is worth keeping. A long-running world grows in memory, and a restart at an hour nobody plays keeps that flat. If your group plays at 05:00 local time, move it, and make sure TZ is set, or the schedule runs in UTC.
Backups inside the container, and outside it#
The image takes its own backups, separate from Valheim's built-in -backups rotation:
| Variable | Default | Meaning |
|---|---|---|
BACKUPS | true | Enable the backup job |
BACKUPS_CRON | 5 * * * * | Hourly, at five past |
BACKUPS_DIRECTORY | /config/backups | Where archives go |
BACKUPS_MAX_AGE | 3 | Delete archives older than this many days |
BACKUPS_MAX_COUNT | 0 | Keep at most this many (0 means no count limit) |
BACKUPS_IF_IDLE | true | Keep backing up while the server is empty |
BACKUPS_IDLE_GRACE_PERIOD | 3600 | Seconds after the last player leaves before idle backups stop |
BACKUPS_ZIP | true | Compress the archives |
Hourly, kept for three days, is a reasonable default: 72 snapshots of a world that is rarely more than 100 MB compressed. The backups are taken from the files on disk, so they contain the world as of the last save, not the last minute of play; a shorter -saveinterval in SERVER_ARGS makes them fresher.
The weakness is location. /config/backups sits on the same disk as the world it protects. A dead disk, a deleted folder or a compromised host takes both. Copy the archives off the machine - to another server, to object storage with rclone or the AWS CLI, anywhere else - on a schedule of your own. The image offers POST_BACKUP_HOOK, a command it runs after each archive with the file's path substituted for @BACKUP_FILE@, which is a tidy place to hang an upload. Game server backups to S3 shows the upload side, and Valheim server backups and world corruption covers restoring and recognising a corrupt world.
To restore, stop the container, unzip the archive you want into ./config/worlds_local/ over the current .db and .fwl pair (keep a copy of the current pair first), and start it.
BepInEx and mods in the container#
Set BEPINEX=true and the image downloads the BepInExPack for Valheim on start and launches the server through it. Mod .dll files then go in the plugins folder under /config/bepinex/ on current versions of the image - check the README for the exact path if the folder is not where you expect - and their configuration files appear under /config/bepinex/config after the first start with the mod loaded. Restart the container after adding or removing a mod.
BEPINEX=trueUPDATE_CRON=There is also VALHEIM_PLUS=true, which installs Valheim Plus instead. The original Valheim Plus project has not kept pace with the game for a long time, and most groups now use maintained forks or individual mods; do not enable it unless you know the version you get works with the current game. Never enable both.
Everything else about mods is the same as on a native server: mods that change game rules must be installed at the same version on every client, Xbox players cannot join a server that needs client mods, and Jotunn-based mods kick anyone whose versions do not match. Valheim mods with BepInEx on a server covers which mods go where, and mod version mismatch kicks covers the most common failure.
Troubleshooting the container#
The container starts but nobody can connect. The port mapping needs /udp; "2456-2457:2456-2457" without it maps TCP and Valheim gets nothing. Then check the host firewall - Docker inserts its own iptables rules, but a cloud provider's firewall still needs UDP 2456-2457 open. Valheim ports explained has the full list.
The world resets on every recreate. /config is not mounted, or is mounted to a different folder than last time. docker compose down followed by up destroys the container but never a bind-mounted folder.
Progress lost after `docker compose down`. The grace period was too short and the server was killed mid-save. Set stop_grace_period: 2m and watch the logs on the next stop for the save completing.
Out-of-memory restarts. A container with a memory limit that the world outgrows is killed without saving. Either raise the limit or remove it; how much RAM a Valheim server needs has realistic figures.
Files in `./config` owned by root. PUID and PGID were left at 0. Set them and chown the existing folders once.
On RE:NODE every server already runs in its own container, so you get the same isolation without maintaining an image: the launch arguments are fields on the Startup tab, a stop is always a clean save, and backups go off the machine and restore with a button.
FAQ#
Is lloesche/valheim-server an official image?
No. It is a community project, not published by Iron Gate or Valve. It is popular and maintained, but its README is the only authority on its variables and behaviour.
How do I see the join code for crossplay in Docker?
Add -crossplay to SERVER_ARGS, restart, and read it from docker compose logs. The code is printed on every start and changes each time.
How do I run a command on the server console?
Vanilla Valheim has no console input on the server and no RCON. Admin commands are typed in-game with F5 by a player in adminlist.txt, which works the same in a container.
Can I run two Valheim servers with this image?
Yes. Use two services with different container_names, separate config and data folders, different SERVER_PORT values at least three apart, and port mappings to match. Both can share one machine as long as it has the memory.
Does Docker add lag?
Not noticeably. Port mapping adds a few microseconds per packet, far below anything a player can see. Use network_mode: host only if you have a reason; it removes the mapping but also the isolation.


