Valheim is an enjoyable (and rather addictive) game about Vikings, building things, and killing bosses. Playing it alone is perfectly fine, but it's best with friends. The most reliable way to do this is to host your own server — and using your own hardware is the cheapest and most controllable option. I've found that in both performance-per-watt and price-per-performance, the M1 Mac mini makes for a surprisingly capable server. Trying to get it running on Apple Silicon did take a bit of research, since as of writing Valheim only natively supports x86 processors. Fortunately, Apple Silicon handles x86 emulation well enough that you can host a server without trouble even on the base 8GB M1 Mac mini. Here's how to do it.

Table of Contents

Necessary software

Configure your Valheim VM and container environment

Before starting your VM instance, you'll need to configure your VM and container manifests. First, create a project folder:

mkdir -p $HOME/valheim-configs
cd $HOME/valheim-configs

Set up Lima VM

Lima VM is the best option I've found for proper QEMU emulation, which fully emulates x86 architecture rather than the partial emulation you might get from Docker Desktop or Parallels. Lima is also considerably more lightweight than fuller VMs like UTM or VirtualBox.

To install Lima, you'll need Homebrew first. Follow the instructions on the Homebrew site to install brew, then run brew install lima. Once that's done, install socket_vmnet — which handles port forwarding from the VM through to the host — by running brew install socket_vmnet. You're then ready to configure the VM manifest.

Lima config

Create the following .yaml manifest, titled valheim-server.yaml:

# full QEMU emulation
arch: 'x86_64'
# must be minimum 50GB
disk: 50GiB
# amount needed depends on player count
# generally half of what Intel needs
# recommend at least 4GiB
memory: 6GiB
# recommend at least 3 cpus
cpus: 4

# bridged creates separate IP on your LAN with the hostname "lima-<vm-name>"
networks:
- lima: bridged

images:
- location: "https://cloud-images.ubuntu.com/releases/22.04/release-20221201/ubuntu-22.04-server-cloudimg-amd64.img"
  arch: "x86_64"
  digest: "sha256:8a814737df484d9e2f4cb2c04c91629aea2fced6799fc36f77376f0da91dba65"
- location: "https://cloud-images.ubuntu.com/releases/22.04/release-20221201/ubuntu-22.04-server-cloudimg-arm64.img"
  arch: "aarch64"
  digest: "sha256:8a0477adcbdadefd58ae5c0625b53bbe618aedfe69983b824da8d02be0a8c961"
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
  arch: "x86_64"
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img"
  arch: "aarch64"

mounts:
- location: "~"
- location: "/tmp/lima"
  writable: true
containerd:
  system: false
  user: false
provision:
- mode: system
  script: |
    #!/bin/sh
    sed -i 's/host.lima.internal.*/host.lima.internal host.docker.internal/' /etc/hosts
- mode: system
  script: |
    #!/bin/bash
    set -eux -o pipefail
    command -v docker >/dev/null 2>&1 && exit 0
    export DEBIAN_FRONTEND=noninteractive
    curl -fsSL https://get.docker.com | sh
    systemctl disable --now docker
    apt-get install -y uidmap dbus-user-session
- mode: user
  script: |
    #!/bin/bash
    set -eux -o pipefail
    systemctl --user start dbus
    dockerd-rootless-setuptool.sh install
    docker context use rootless
probes:
- script: |
    #!/bin/bash
    set -eux -o pipefail
    if ! timeout 30s bash -c "until command -v docker >/dev/null 2>&1; do sleep 3; done"; then
      echo >&2 "docker is not installed yet"
      exit 1
    fi
    if ! timeout 30s bash -c "until pgrep rootlesskit; do sleep 3; done"; then
      echo >&2 "rootlesskit (used by rootless docker) is not running"
      exit 1
    fi
  hint: See "/var/log/cloud-init-output.log" in the guest
hostResolver:
  hosts:
    host.docker.internal: host.lima.internal
portForwards:
- guestSocket: "/run/user/{{.UID}}/docker.sock"
  hostSocket: "{{.Dir}}/sock/docker.sock"
message: |
  To run docker on the host (assumes docker-cli is installed):
  ------
  docker context create lima-{{.Name}} --docker "host=unix://{{.Dir}}/sock/docker.sock"
  docker context use lima-{{.Name}}
  docker run hello-world
  ------

Container config

Now create a Docker Compose manifest titled docker-compose.yaml:

version: "3"


services: 
  valheim: 
    image: ghcr.io/lloesche/valheim-server
    cap_add:
      - sys_nice
    volumes: 
      - $HOME/valheim-server/config:/config
      - $HOME/valheim-server/data:/opt/valheim
      - /Users/$USER/.ssh/id_ecdsa_valheim:/root/.ssh/id_ecdsa:ro
      - /Users/$USER/.ssh/known_hosts:/root/.ssh/known_hosts
    ports: 
      - "2456-2457:2456-2457/udp"
    env_file:
      - ./valheim.env
    restart: always
    stop_grace_period: 2m

And create a valheim.env file alongside it:

SERVER_NAME=ServerName
WORLD_NAME=WorldName
SERVER_PASS=securepassword
SERVER_PUBLIC=true
TZ='Europe/London'
POST_BACKUP_HOOK='timeout 300 cp @BACKUP_FILE@ $HOME/backups/$(basename @BACKUP_FILE@)'

Make sure to set the TZ variable to your time zone. Auto-restart and auto-update both rely on knowing the correct time, so they run in the small hours rather than mid-afternoon.

The POST_BACKUP_HOOK variable can be used to copy your world backups somewhere else after each local backup — either to your host machine via scp, or to a remote file server for extra redundancy.

If you'd like to copy backups back to your host machine, there's already a volume reference to /Users/$USER/.ssh/id_ecdsa_valheim in the compose file. To generate that key, run:

ssh-keygen -t ecdsa -f /Users/$USER/.ssh/id_ecdsa_valheim -N ''

Deploy your Valheim server container

Initialise your Lima VM with:

export LIMA_INSTANCE='valheim-server' &&
limactl start --name=$LIMA_INSTANCE $HOME/valheim-server/valheim-server.yaml

(You can add that export to your .zshrc as well, otherwise it resets on each new shell.)

Once the VM has finished initialising, enter it with:

lima

Now copy over your compose and env files:

mkdir -p ~/valheim-server &&
cd ~/valheim-server &&
cp /Users/$USER/valheim-configs/docker-compose.yaml . &&
cp /Users/$USER/valheim-configs/valheim.env . &&
docker compose up -d --remove-orphans

Starting the container will create two folders inside ~/valheim-server/: config/ and data/. These hold all your server and world data, so don't delete them.

Watch the logs with:

docker logs -f valheim-server-valheim-1

You'll see Waiting for server to listen on UDP port 2456 for the first couple of minutes. Once you see Server is now listening on UDP port 2456, the server is ready. There's one last step before it's reachable from the internet: forwarding your ports.

Access your server from the Internet

Forward ports

Once your Lima instance is running, a new IP will appear on your local network with a hostname like lima-valheim-server. Forward UDP ports 2456–2457 from that IP on your router. The exact steps vary by router, so check your documentation.

Check server status and logs

The easiest way to verify your ports are open and the server is working is to use https://geekstrom.de/valheim/check/ (your server needs to be set to public for this). Once you see a healthy status, you're good to go.

Join your server

After starting the game, select "Start Game," pick or create your character, then go to the "Join Game" tab and select "Add Server."

Enter your server name, press Return, then select "Connect."

After a few seconds, you'll see an "Enter Password" prompt. Use the password you set in valheim.env.

Once your world loads, you're ready to go.

Conclusion

Apple Silicon turns out to be a surprisingly capable server platform — the level of sustained performance I've got from an 8GB M1 Mac mini, at that sort of power draw, is genuinely impressive. Hopefully this gets your server up and running so you can get on with building things and killing bosses.

Troubleshooting

Server says it's listening but it's still inaccessible

  • Check that your ports are open on your router, and that you're forwarding from the guest IP (lima-*.lan) rather than your host IP.
  • Stop the server, run brew reinstall socket_vmnet, and start it again. The socket_vmnet service occasionally doesn't initialise cleanly.
  • Don't run socket_vmnet as a service on your host machine. The Homebrew install instructions suggest this, but Lima starts its own instance when launching the VM.
  • Make sure you have at least 50GB of free space for your VM disk.

Container error mentioning incorrect architecture

  • Make sure you've included arch: x86_64 in your Lima config. Without it, Lima defaults to fast (partial) emulation rather than the full emulation we need here.

Anything else

Open an issue on lloesche/valheim-server or lima-vm/lima as appropriate.