How to Set Up a Headless Steam Gaming Server with Docker in 15 Minutes
A step-by-step guide to deploying a complete headless Steam server using Docker Compose. Learn how to configure GPU passthrough, persist game libraries, and stream games remotely via noVNC, Moonlight, or Steam Remote Play from any device.

Want to Play AAA Games on a Weak Laptop? Run a Cloud Steam Server on Your NAS or VPS
Last week, while traveling, I wanted to play some Elden Ring in my hotel room. The only device available was a sluggish hotel laptop with no dedicated GPU. Opening Steam, I realized the cloud save synced perfectly, but the local hardware couldn't even run the launcher smoothly.
That's when it clicked: Games don't need to run locally. As long as the video stream can reach you, you're good.
This tutorial walks you through deploying a complete headless Steam service on an idle Linux server (your home NAS, VPS, or an old PC with integrated graphics) using Docker. Once set up, you can play directly in any web browser, with full audio support, or stream via Steam Link or Moonlight for a premium low-latency experience.
By the end of this guide, you'll have your own cloud gaming stream server, ready to play on your phone, tablet, or office laptop anytime, anywhere.
Prerequisites
Before we begin, ensure your environment meets the following requirements:
- A Linux Host: Ubuntu 20.04+ or Debian 11+ is recommended. It should have a reachable public or local IP.
- Docker & Docker Compose: Installed and running. If not, you can install Docker quickly with the official script:
curl -fsSL https://get.docker.com | sh. - GPU Requirements (Choose one):
- NVIDIA Dedicated GPU (Recommended): Drivers installed and NVIDIA Container Toolkit configured.
- No dedicated GPU?: AMD/Intel integrated graphics or pure CPU mode will also work, though performance for AAA titles may vary.
- Network Connectivity: Your client devices (phone/laptop) must be able to reach the server's IP and ports.
Note: If you're missing NVIDIA drivers, you can simply skip the GPU-related configurations below. The project natively supports CPU-only mode, though streaming frame rates will be lower.
Quick Start: One-Click Deployment with Docker Compose
I've tested this project extensively, and its biggest advantage is out-of-the-box readiness. You don't need to manually configure Wine, mess with Proton, or set up a virtual desktop from scratch. Everything is packaged into a single Docker image.
Step 1: Create Project Directory and Compose File
Navigate to a directory with sufficient disk space and create a docker-compose.yml file:
bash
mkdir ~/steam-headless && cd ~/steam-headless
mkdir -p ~/games # Directory to store game files, create as needed
Step 2: Write the Compose Configuration
This is the core of the deployment. Here's a streamlined, production-ready configuration with comments explaining every critical parameter:
yaml
version: "3.8"
services:
steam-headless:
image: lscr.io/linuxserver/steam-headless:latest
container_name: steam-headless
restart: unless-stopped
# Uncomment the block below if you're using an NVIDIA GPU
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
environment:
- PUID=1000 # Replace with your host user's UID (run `id` to check)
- PGID=1000 # Replace with your host user's GID
- TZ=Asia/Shanghai # Set your timezone
- WEBPAGE_TITLE=MyGameServer
ports:
- "8080:8080" # Web UI access port (noVNC)
- "8443:8443" # Web UI HTTPS port
- "47984:47984/tcp" # Moonlight streaming
- "47989:47989/tcp"
- "48010:48010/tcp"
- "47998:47998/udp"
- "47999:47999/udp"
- "48000:48000/udp"
- "48010:48010/udp"
volumes:
- ./config:/config # Persist configuration
- ./data:/home/default # Persist user data
- ~/games:/mnt/games # Game library path
shm_size: "1gb" # Shared memory required for Web UI rendering
Why this configuration? Key points to note:
PUID/PGID: Docker defaults to running as root, but this project maps to your specified user permissions to avoid file permission conflicts. Runidin your host terminal to find your UID/GID (usually1000for standard users)./mnt/gamesvolume: The documentation explicitly notes that only paths mounted to/mnt/gameswill be auto-detected by Steam as a library. Other paths might be wiped on container updates.shm_size: The noVNC virtual desktop relies on shared memory for rendering. The default is too small and will cause lag or black screens. 1GB is the official recommendation.- Ports:
8080is for direct browser access. Moonlight requires additional TCP/UDP ports. If you don't plan to use Moonlight, you can safely remove them and keep only8080.
Step 3: Start the Service
bash
docker compose up -d
The first run will pull the image (~3–4 GB). Once complete, open http://<YOUR_SERVER_IP>:8080 in your browser. You should see an Xfce4 desktop with the Steam client launching automatically.
Basic deployment is complete. Let's actually play something.
Hands-On: Downloading Games & Remote Streaming
Step 1: Log in to Steam
In the Web UI virtual desktop, click the Steam icon (pre-configured with Proton to run Windows games on Linux). Enter your Steam credentials.
Tip: For security, strongly consider enabling Steam Guard. Typing in a Web UI can be cumbersome, so generate the verification code via the Steam mobile app on your phone first.
Step 2: Add the Game Library Path
After logging in, go to Steam → Settings → Storage. Add /mnt/games as your default installation directory. This ensures game files are stored on the host-mounted volume, surviving container rebuilds.
Step 3: Download & Launch a Game
Pick a game from your library (I tested with Stardew Valley for its small size and low requirements), click Install, and ensure it downloads to /mnt/games.
Once installed, click Play. The game will launch inside the virtual desktop. You now have two ways to play:
- Direct Browser Play: Control the game directly in the noVNC interface. Video and audio are streamed through your browser. Ideal for casual play or quick sessions.
- Moonlight Streaming: If your server has an NVIDIA GPU, install Moonlight Sunshine via Flatpak (supported by the container). Then, install the Moonlight client on your phone/TV/PC and connect to
<SERVER_IP>. Latency and visual quality will improve significantly.
Step 4: Stream via Steam Remote Play to Another PC
For a more elegant workflow (e.g., playing at the office), log into the same Steam account on your office PC. In your library, click Stream (instead of Play) next to the game. Steam will automatically discover your headless server, offloading computation to it while your local machine acts as a display and input receiver.
⚠️ Important Caveat: If the two machines aren't on the same LAN, you must assign a dedicated IP to the Docker container. Otherwise, Steam assumes a cross-network relay, routing traffic through Valve's cloud servers and causing massive latency. The fix is to create a custom Docker network with a static IP, or use a zero-trust network tool like ZeroTier/Tailscale.
Troubleshooting & Pro Tips
Q1: Web UI shows a black screen or loads slowly
This is almost always due to insufficient shared memory. Verify shm_size: "1gb" exists in your docker-compose.yml. If missing, add it and run docker compose down && docker compose up -d.
Q2: NVIDIA GPU isn't detected
Ensure nvidia-container-toolkit is installed on the host. On Ubuntu:
bash
sudo apt install nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Then uncomment the GPU block in your Compose file.
Q3: Game save files disappeared
By default, only /config, /home/default, and /mnt/games are persisted. If a game stores saves elsewhere, they'll vanish on container rebuild. Fix this by symlinking the save directory to /home/default inside the virtual desktop.
Q4: Want to install other launchers (Lutris / Heroic / EmuDeck)?
In the virtual desktop, open Applications > System > Software to search and install them directly. Alternatively, place a .sh installation script in /home/default/init.d/ inside the container; it will auto-execute on the next startup.
Q5: Chinese input method support
noVNC's default virtual keyboard lacks robust IME support. If your game requires typing (e.g., MMORPGs), install an input framework like fcitx inside the container and configure it via the desktop's system tray.
Wrap-Up
Here's what we accomplished today:
- Deployed a full headless Steam server using Docker Compose.
- Configured and accessed Steam remotely via a noVNC Web UI.
- Mounted a host game library to
/mnt/gamesfor data persistence. - Explored Moonlight and Steam Remote Play for different streaming scenarios.
- Resolved common pitfalls like black screens, GPU passthrough, and save file loss.
What makes this project stand out is that it abstracts away the most tedious parts (Wine/Proton setup, virtual desktop configuration, audio bridging). You only need to tune a few Docker parameters and mount paths.
Where to go from here? Transform a NAS or idle mini PC into a dedicated home gaming hub. Combine it with Tailscale for secure remote access, pair a Bluetooth controller with Moonlight, and you'll have an experience nearly indistinguishable from a local console.
Give it a try. Dust off that old laptop or repurpose your NAS tonight—your personal cloud gaming server is waiting.