docs/portainer-cs2.md beschreibt Stack, einmaligen Download, Updates ohne Neu-Download, Anbindung per Static- oder Docker-Adapter, mehrere Server und Fehlersuche. Standard-Image auf joedwards32/cs2 umgestellt, da cm2network/cs2 nicht mehr gepflegt wird.
11 KiB
CS2-Server mit Portainer einrichten
Diese Anleitung richtet einen CS2-Dedicated-Server als Portainer-Stack ein, dessen Spieldateien (ca. 60 GB) einmal heruntergeladen und danach dauerhaft in einem Docker-Volume gespeichert werden. Neustarts, Image-Updates und neue Matches laden die Dateien nicht neu.
Anschließend wird der Server mit CS2 Mapveto verbunden.
So funktioniert das Speichern der Dateien
Das Image joedwards32/cs2 installiert das Spiel mit SteamCMD nach /home/steam/cs2-dedicated/. Bei jedem Containerstart prüft es mit app_update, ob es ein Update gibt.
- Beim ersten Start ist das Verzeichnis leer, also werden die kompletten ~60 GB geladen.
- Bei jedem weiteren Start liegen die Dateien schon im Volume. SteamCMD vergleicht nur die Version, das dauert Sekunden bis wenige Minuten.
- Wenn Valve ein Update veröffentlicht, lädt SteamCMD nur die geänderten Teile (Delta-Update).
Entscheidend ist also: Das Verzeichnis /home/steam/cs2-dedicated/ muss auf einem benannten Volume (oder einem Host-Ordner) liegen, nicht im Container selbst. Dann übersteht es auch das Löschen und Neuerstellen des Containers.
Hinweis zum Image:
cm2network/cs2ist die ältere Fassung desselben Projekts und wird seit September 2025 nicht mehr aktualisiert. Verwendejoedwards32/cs2(auch alsghcr.io/joedwards32/cs2). Die Umgebungsvariablen sind identisch.
Voraussetzungen
- Docker-Host mit Portainer (CE reicht), Linux empfohlen
- ca. 70 GB freier Speicher für Spieldateien und Updates, idealerweise auf einer SSD
- Mindestens 2 CPU-Kerne und 4 GB RAM pro gleichzeitig laufendem Server
- Game Server Login Token (GSLT) für App-ID 730: https://steamcommunity.com/dev/managegameservers. Ohne Token ist der Server nur im LAN-Modus nutzbar (siehe unten). Pro gleichzeitig laufendem Server brauchst du ein eigenes Token.
- Freigegebene Ports (Router/Firewall), pro Server:
- Spielport, Standard 27015, UDP und TCP. Über TCP läuft auch RCON, das nur das Backend erreichen muss.
- CSTV-Port, Standard 27020, UDP
Schritt 1: Stack in Portainer anlegen
- Portainer öffnen, die richtige Environment wählen (dein Docker-Host).
- Links auf Stacks und dann + Add stack klicken.
- Name:
cs2 - Build method: Web editor. Diesen Inhalt einfügen:
services:
cs2-1:
image: joedwards32/cs2:latest
container_name: cs2-1
restart: unless-stopped
environment:
- SRCDS_TOKEN=${SRCDS_TOKEN} # GSLT aus Schritt "Voraussetzungen"
- STEAMAPPVALIDATE=0 # 0 = nur Update prüfen, NICHT jedes Mal alles verifizieren
- CS2_SERVERNAME=MaxLAN CS2 #1
- CS2_PORT=27015
- CS2_RCONPW=${CS2_RCONPW} # RCON-Passwort, wird in CS2 Mapveto eingetragen
- CS2_PW=${CS2_PW} # Serverpasswort (CS2 Mapveto setzt pro Match ein eigenes)
- CS2_MAXPLAYERS=12
- CS2_SERVER_HIBERNATE=0 # kein Ruhemodus, sonst reagiert RCON verzögert
- CS2_LAN=0 # 1 = reiner LAN-Betrieb (siehe unten)
- CS2_GAMETYPE=0
- CS2_GAMEMODE=1
- CS2_MAPGROUP=mg_active
- CS2_STARTMAP=de_dust2
- TV_ENABLE=1 # CSTV für Zuschauer und Stream
- TV_PORT=27020
- TV_PW=${TV_PW}
- TV_RELAY_PW=${TV_PW}
- TV_DELAY=90
- TV_AUTORECORD=0
volumes:
- cs2-gamefiles:/home/steam/cs2-dedicated/
ports:
- "27015:27015/tcp"
- "27015:27015/udp"
- "27020:27020/udp"
stdin_open: true
tty: true
volumes:
cs2-gamefiles:
# Fester Name, damit Portainer keinen Stack-Präfix voranstellt
# und CS2 Mapveto (Docker-Adapter) dasselbe Volume findet.
name: cs2-mapveto-gamefiles
-
Darunter bei Environment variables auf + Add an environment variable klicken und anlegen:
Name Wert SRCDS_TOKENdein GSLT CS2_RCONPWlanges, zufälliges Passwort (nur Buchstaben und Ziffern) CS2_PWbeliebiges Serverpasswort TV_PWPasswort für CSTV So stehen die Zugangsdaten nicht im Stack-Text selbst.
-
Deploy the stack klicken.
Schritt 2: Ersten Download abwarten
- In Portainer auf Containers und dann cs2-1 klicken, danach auf Logs.
- Ein Haken bei Auto-refresh logs hilft. Du siehst den SteamCMD-Fortschritt, z. B.
Update state (0x61) downloading, progress: 12.34 %. - Je nach Leitung dauert das 30 Minuten bis mehrere Stunden. Den Container in dieser Zeit nicht neu starten.
- Fertig ist der Server, wenn im Log
Success! App '730' fully installed.steht und danach der Serverstart folgt. Am Ende stehen Zeilen wieGC Connection established for serverbzw.Connection to Steam servers successful.
Ab jetzt liegen die Dateien im Volume cs2-mapveto-gamefiles. Unter Volumes siehst du es mit etwa 60 GB.
Test: In der CS2-Konsole connect <Host-IP>:27015; password <CS2_PW> eingeben.
Schritt 3: Updates und Neustarts ohne neuen Download
| Aktion in Portainer | Was passiert mit den Dateien |
|---|---|
| Container Restart | bleiben erhalten, nur Update-Prüfung |
| Stack Update the stack (z. B. geänderte Variablen) | bleiben erhalten |
| Re-pull image and redeploy | neues Image, Dateien bleiben im Volume |
| Container Remove und neu deployen | bleiben erhalten (Volume ist unabhängig) |
| Stack löschen | Volume bleibt standardmäßig bestehen |
| Volume löschen (Volumes, Remove) | Dateien sind weg, nächster Start lädt 60 GB neu |
Wenn Valve CS2 aktualisiert, lehnen Server mit alter Version Spieler ab. Dann reicht ein Restart des Containers, er zieht nur das Delta.
Setze
STEAMAPPVALIDATEnur auf1, wenn Dateien beschädigt sind. Dann werden alle 60 GB geprüft, was sehr lange dauert. Danach wieder auf0stellen.
Schritt 4: Mit CS2 Mapveto verbinden
Für einen dauerhaft laufenden Server wie diesen ist der Static-Adapter gedacht. CS2 Mapveto reserviert ihn pro Match, setzt Passwörter und Map per RCON und gibt ihn nach dem Match wieder frei. Der Container läuft dabei durchgehend.
In der .env von CS2 Mapveto:
SERVER_ADAPTER=static
# host:spielport:rconPasswort:cstvPort
CS2_STATIC_SERVERS=192.168.178.50:27015:DEIN_CS2_RCONPW:27020
192.168.178.50durch die IP des Docker-Hosts ersetzen, die Spieler erreichen. Sie wird ihnen als Verbindungsadresse angezeigt.- Das Backend von CS2 Mapveto muss dieselbe Adresse per TCP auf dem Spielport erreichen (RCON).
- Danach CS2 Mapveto neu starten (
npm run devbzw.npm start).
Prüfen: Ein neues Match erstellen. Im Serverpanel muss nach wenigen Sekunden „Server bereit“ erscheinen, ohne „Demo“. Bleibt es bei „Server startet“, zeigt die Meldung darunter den RCON-Fehler, z. B. falsches Passwort oder Port nicht erreichbar.
Mehrere Matches gleichzeitig
Jeder gleichzeitig laufende Server braucht eigene Ports, ein eigenes GSLT und, wichtig, ein eigenes Installationsverzeichnis. Zwei Container dürfen nicht gleichzeitig dasselbe Volume aktualisieren, sonst können sich SteamCMD-Downloads gegenseitig beschädigen.
Zweiten Server im selben Stack ergänzen:
cs2-2:
image: joedwards32/cs2:latest
container_name: cs2-2
restart: unless-stopped
environment:
- SRCDS_TOKEN=${SRCDS_TOKEN_2}
- STEAMAPPVALIDATE=0
- CS2_SERVERNAME=MaxLAN CS2 #2
- CS2_PORT=27025
- CS2_RCONPW=${CS2_RCONPW}
- CS2_PW=${CS2_PW}
- CS2_SERVER_HIBERNATE=0
- TV_ENABLE=1
- TV_PORT=27030
- TV_PW=${TV_PW}
- TV_DELAY=90
volumes:
- cs2-gamefiles-2:/home/steam/cs2-dedicated/
ports:
- "27025:27025/tcp"
- "27025:27025/udp"
- "27030:27030/udp"
stdin_open: true
tty: true
…und unter volumes: ergänzen:
cs2-gamefiles-2:
name: cs2-mapveto-gamefiles-2
In CS2 Mapveto beide eintragen:
CS2_STATIC_SERVERS=192.168.178.50:27015:PASSWORT:27020,192.168.178.50:27025:PASSWORT:27030
Damit der zweite Server nicht erneut 60 GB aus dem Internet lädt, gibt es zwei Wege:
- Volume kopieren (einmalig): Server 1 in Portainer stoppen, dann auf dem Docker-Host:
Danach Server 1 wieder starten und Server 2 deployen. Er findet die Dateien schon vor und prüft nur noch die Version.
docker volume create cs2-mapveto-gamefiles-2 docker run --rm -v cs2-mapveto-gamefiles:/from -v cs2-mapveto-gamefiles-2:/to alpine sh -c "cp -a /from/. /to/" - Lancache im LAN betreiben, typisch für LAN-Partys: Steam-Downloads werden lokal zwischengespeichert. Jeder weitere Server lädt dann mit LAN-Geschwindigkeit. Im Stack dafür beim Service
dns: - <Lancache-IP>ergänzen (siehe Beispiel des Images).
Alternative: CS2 Mapveto startet Container selbst (Docker-Adapter)
Statt dauerhaft laufender Server kann CS2 Mapveto pro Match einen eigenen Container starten und danach wieder entfernen (SERVER_ADAPTER=docker). Damit auch dabei nichts neu geladen wird, nutzt der Adapter das Volume cs2-mapveto-gamefiles. Das ist genau das Volume, das der Stack oben angelegt und befüllt hat.
SERVER_ADAPTER=docker
CS2_GSLT=dein_gslt
CS2_PUBLIC_HOST=192.168.178.50
CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles
CS2_DOCKER_IMAGE=joedwards32/cs2:latest
Wichtig dabei:
- Den Stack-Server vorher stoppen, damit nicht zwei Container gleichzeitig dasselbe Volume aktualisieren. Der Stack dient dann nur zum einmaligen Befüllen und für Updates.
- Für gleichzeitige Matches gilt dasselbe Problem: Alle Match-Container teilen sich das Volume und prüfen beim Start auf Updates. Das ist unkritisch, solange kein Valve-Update ansteht. Kurz nach einem CS2-Patch nur ein Match auf einmal starten.
- Das Backend braucht Zugriff auf den Docker-Daemon (es läuft auf demselben Host bzw. hat
/var/run/docker.sock).
Für eine LAN-Party mit festen Servern ist der Static-Adapter mit Stack aus Schritt 1 die einfachere und robustere Wahl.
LAN-Betrieb ohne GSLT
Für reine LAN-Veranstaltungen kann CS2_LAN=1 gesetzt werden (sv_lan 1). Der Server ist dann nur im lokalen Netz erreichbar und braucht kein Token. Spieler müssen im selben Netz sein. Für Zuschauer über das Internet oder externe Teams ist ein GSLT nötig.
Fehlersuche
| Symptom | Ursache / Lösung |
|---|---|
| Download startet bei jedem Neustart von vorn | Volume fehlt oder ist falsch eingehängt. Prüfen, ob /home/steam/cs2-dedicated/ auf ein Volume zeigt (Container, Volumes). |
Permission denied beim Download |
Bei Host-Ordnern statt Volume: chown -R 1000:1000 /pfad/zum/ordner, weil der Container als Benutzer steam (UID 1000) läuft. |
Error! App '730' state is 0x202 |
Zu wenig Speicher. Mindestens 70 GB frei halten. |
| Spieler werden mit „Server läuft auf älterer Version“ abgewiesen | Valve-Update: Container neu starten. |
| CS2 Mapveto meldet „RCON-Passwort wurde abgelehnt“ | CS2_RCONPW im Stack und Passwort in CS2_STATIC_SERVERS vergleichen. |
| CS2 Mapveto meldet „Zeitüberschreitung“ | TCP-Port 27015 vom Backend aus nicht erreichbar (Firewall) oder Server lädt noch. |
| Server nicht in der Serverliste, aber per IP erreichbar | GSLT fehlt oder ist ungültig. Token prüfen, pro Server ein eigenes. |