Files
2026-09-21 01:32:08 -07:00

8.4 KiB

Valheim Server StackScript

A Linode StackScript that turns a freshly created Debian 13 Linode into a Valheim dedicated server. Running it as a StackScript means everything happens as root during deployment, so no SSH session (and therefore no SSH key) is needed to configure the machine.

It does four things, in order:

  1. Installs Docker from Debian's own docker.io package.
  2. Mounts the pre-existing Valheim volume (this mirrors voldirections.txt; mkfs.ext4 is not run because the filesystem already exists).
  3. Creates the persistent config/ and data/ directories on the volume.
  4. Starts the Valheim server container with those directories bind-mounted, so worlds, configuration and backups survive destroying and recreating the Linode.

Instructions

  1. Log in to the Linode Cloud Manager and open StackScripts.
  2. Click Create StackScript.
  3. Set Label to something like valheim-server and choose Debian 13 (or "Any") as the Target Images.
  4. Paste the code block below into the Script editor.
  5. Edit the server settings at the top of the script (name, world, password) before saving.
  6. Leave the UDF (User Defined Fields) section empty — the values are hard-coded, so nothing needs to be entered at deploy time.
  7. Click Save. The numeric ID of the StackScript appears in the URL and in the StackScript list; copy it.
  8. Put that ID in create-linode.sh as the STACKSCRIPT_ID value near the top of the file.

After that, create-linode.sh c <label> passes the StackScript on the create call and the Linode comes up as a running Valheim server with no further steps.

Where things live on the volume

Everything the container needs to keep is under /mnt/ValVol202609/valheim:

Host path Container path Contents
/mnt/ValVol202609/valheim/config /config World saves, adminlist.txt, bannedlist.txt, permittedlist.txt, backups/
/mnt/ValVol202609/valheim/data /opt/valheim The ~1 GB downloaded server plus SteamCMD's depot cache

Mounting /opt/valheim as well means a newly created Linode does not have to re-download the server from Steam, which saves several minutes per instance. The depot cache lives inside that same directory so it stays in sync with the installation it belongs to.

Worlds are stored in config/worlds_local/<WORLD_NAME>/ (older releases used a single <WORLD_NAME>.db/.fwl pair directly in config/worlds/). To bring an existing world along, copy it into the worlds_local directory on the volume and set WORLD_NAME to match.

Notes

  • Ports. The container publishes 2456-2457/udp (game and query). Linodes have no inbound firewall by default, so nothing else is required. If you add a Cloud Firewall, allow those two UDP ports.
  • Sizing. The upstream project recommends a high-clocked 4 core / 8 GB machine, which is exactly g6-standard-4 — the type create-linode.sh uses.
  • First start. The container downloads ~1 GB from Steam on first boot and takes several minutes to become joinable.
  • Admin. Once the server has started, config/adminlist.txt exists on the volume. Put your SteamID64 in it to get in-game admin commands.
  • Backups. The container writes hourly world backups to config/backups/ on the volume and prunes them after 3 days.
  • Updates. The container checks for Valheim updates every 15 minutes and restarts the server when one is found, but only while nobody is connected.
  • Re-running. The script is safe to run again: the mount and the fstab entry are only created if missing, and any previous container is removed before the new one is started.

Sensitive data

  • There is a secrets file in gitignore
  • The IDs for all linode API calls are linked to my account, but non-sensitive.
  • The passwords are placeholders, edited when setting up StackScripts
  • Repo can be made public, as no git snapshot had sensitive data.

StackScript

#!/bin/bash
# Linode StackScript: turn a fresh Debian 13 Linode into a Valheim dedicated
# server whose worlds, configuration and backups live on the pre-existing
# Valheim volume. Runs as root during deployment; no SSH access required.

set -euo pipefail

# ---------------------------------------------------------------------------
# Server settings - edit these before saving the StackScript.
# ---------------------------------------------------------------------------
SERVER_NAME="Valheim Server"
WORLD_NAME="Dedicated"
# Must be at least 5 characters, otherwise valheim_server.x86_64 refuses to
# start. Anything saved here is readable by anyone with access to the
# StackScript, so change it.
SERVER_PASS="changeme123"
# true lists the server in the in-game community browser.
SERVER_PUBLIC="true"

# ---------------------------------------------------------------------------
# Volume and container settings.
# ---------------------------------------------------------------------------
VOLUME_NAME="ValVol202609"
VOLUME_DEVICE="/dev/disk/by-id/scsi-0Linode_Volume_${VOLUME_NAME}"
VOLUME_MOUNT="/mnt/${VOLUME_NAME}"

# Everything the container has to keep across Linode rebuilds lives here.
VALHEIM_DIR="${VOLUME_MOUNT}/valheim"
CONFIG_DIR="${VALHEIM_DIR}/config"
DATA_DIR="${VALHEIM_DIR}/data"

CONTAINER_NAME="valheim-server"
# The upstream project moved to ghcr.io/community-valheim-tools/valheim-server.
# The Docker Hub image below is still published and can be swapped for it.
IMAGE="lloesche/valheim-server"

# ---------------------------------------------------------------------------
# 1. Install Docker.
# Debian's docker.io package is enough for a single `docker run` and avoids
# adding a third-party apt repository. ca-certificates is listed explicitly
# because --no-install-recommends would otherwise skip it and image pulls
# would fail TLS verification.
# ---------------------------------------------------------------------------
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends docker.io ca-certificates docker-cli
systemctl enable --now docker

# ---------------------------------------------------------------------------
# 2. Mount the Valheim volume.
# The volume is attached over the Linode API once the instance is running, so
# wait for the device to appear. 60 attempts x 5s = 5 minute ceiling.
# ---------------------------------------------------------------------------
for _ in $(seq 1 60); do
  if [ -e "$VOLUME_DEVICE" ]; then
    break
  fi
  echo "Waiting for ${VOLUME_DEVICE} to appear..."
  sleep 5
done

if [ ! -e "$VOLUME_DEVICE" ]; then
  echo "ERROR: ${VOLUME_DEVICE} never appeared. Aborting so that the world is not written to the instance's ephemeral disk." >&2
  exit 1
fi

mkdir -p "$VOLUME_MOUNT"
if ! mountpoint -q "$VOLUME_MOUNT"; then
  mount "$VOLUME_DEVICE" "$VOLUME_MOUNT"
fi

# Mount on every boot, without duplicating the line on re-runs. nofail keeps a
# missing volume from blocking boot; if the volume is not there the mountpoint
# silently becomes a directory on the instance's own disk, which is why the
# check above aborts rather than continuing.
if ! grep -q "$VOLUME_DEVICE" /etc/fstab; then
  echo "${VOLUME_DEVICE} ${VOLUME_MOUNT} ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
fi

# ---------------------------------------------------------------------------
# 3. Create the persistent directories on the volume.
# ---------------------------------------------------------------------------
mkdir -p "$CONFIG_DIR" "$DATA_DIR"

# ---------------------------------------------------------------------------
# 4. Start the Valheim server.
# --restart unless-stopped brings the server back after a reboot.
# --stop-timeout 120 gives the server time to save the world on shutdown.
# --cap-add=sys_nice lets the Steam library raise its own thread priority.
# ---------------------------------------------------------------------------
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true

docker run -d \
  --name "$CONTAINER_NAME" \
  --cap-add=sys_nice \
  --stop-timeout 120 \
  --restart unless-stopped \
  -p 2456-2457:2456-2457/udp \
  -v "${CONFIG_DIR}:/config" \
  -v "${DATA_DIR}:/opt/valheim" \
  -e SERVER_NAME="$SERVER_NAME" \
  -e WORLD_NAME="$WORLD_NAME" \
  -e SERVER_PASS="$SERVER_PASS" \
  -e SERVER_PUBLIC="$SERVER_PUBLIC" \
  -e SERVER_ARGS="-modifier resources more" \
  "$IMAGE"

echo "Valheim server started."
echo "The first start downloads ~1GB from Steam and takes several minutes."
echo "Follow progress with: docker logs -f $CONTAINER_NAME"