Skip to content

Guides

How to Self-Host a Web Retro Gaming Server with Docker (RomM Setup Guide)

Step-by-step RomM deployment guide using Docker Compose. Mount game directories, configure metadata scrapers, and stream retro ROMs in your browser.

2 min read
Topic
Browser emulation
Difficulty
advanced
Spoilers
None

To self-host a web-accessible retro gaming library, deploy RomM via Docker Compose paired with a MariaDB database and a Redis caching layer. RomM scans local directory hierarchies, pulls cover art and metadata from IGDB, and embeds a WebAssembly emulator frontend so users can play retro consoles directly inside any desktop or mobile browser without installing client-side emulators.

RomM Docker Architecture Diagram

Directory Layout & File Permissions

Create the host folder structure before launching the container stack. Using consistent paths prevents permission lockouts during the initial file indexing pass:

mkdir -p ~/romm/{config,resources,library/roms,mysql_data,redis_data}
chmod -R 755 ~/romm

Organize your game files into system subdirectories matching RomM’s platform conventions:

  • ~/romm/library/roms/gba/ (Game Boy Advance)
  • ~/romm/library/roms/snes/ (Super Nintendo)
  • ~/romm/library/roms/genesis/ (Sega Genesis / Mega Drive)
  • ~/romm/library/roms/psx/ (Sony PlayStation 1 .chd or .bin/.cue)

Production docker-compose.yml Configuration

Save the following configuration inside ~/romm/docker-compose.yml. Replace placeholder passwords with dedicated 32-character random strings:

services:
  romm:
    image: ghcr.io/rommapp/romm:latest
    container_name: romm
    restart: unless-stopped
    environment:
      - DB_HOST=romm-db
      - DB_NAME=romm
      - DB_USER=romm_user
      - DB_PASSWD=ReplaceWithStrongDatabasePassword_8921#
      - ROMM_AUTH_SECRET_KEY=GenerateRandomHexKey32CharsLongHere99!
      - REDIS_HOST=romm-redis
      - IGDB_CLIENT_ID=${IGDB_CLIENT_ID:-}
      - IGDB_CLIENT_SECRET=${IGDB_CLIENT_SECRET:-}
      - DISABLE_USER_REGISTRATION=false
    volumes:
      - ./resources:/romm/resources
      - ./config:/romm/config
      - ./library/roms:/romm/library/roms:ro
    ports:
      - "8080:8080"
    depends_on:
      romm-db:
        condition: service_healthy
      romm-redis:
        condition: service_started

  romm-db:
    image: mariadb:11.2
    container_name: romm-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=ReplaceWithRootPasswordSecure_990!
      - MYSQL_DATABASE=romm
      - MYSQL_USER=romm_user
      - MYSQL_PASSWORD=ReplaceWithStrongDatabasePassword_8921#
    volumes:
      - ./mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

  romm-redis:
    image: redis:7.2-alpine
    container_name: romm-redis
    restart: unless-stopped
    volumes:
      - ./redis_data:/data

Running and Indexing

Launch the multi-container stack in detached mode:

cd ~/romm
docker compose up -d

Verify that all three containers are healthy:

docker compose ps

Open http://<your-server-ip>:8080 in your browser. Register your initial administrator profile, then navigate to Settings -> Scan Library to trigger automated ROM hashing and IGDB metadata scraping.

Reverse Proxy: Solving Browser Audio Stutter (SharedArrayBuffer)

Modern browser-based emulators depend on SharedArrayBuffer for multi-threaded audio synchronization. Modern browsers disable this feature unless the origin is secure (HTTPS) and sends specific cross-origin isolation headers. Without these headers, WebAssembly audio drops into a single-threaded fallback loop that results in severe sound crackling and input latency.

Nginx Configuration

Add these headers to your server block:

server {
    listen 443 ssl http2;
    server_name retro.yourdomain.com;

    # SSL certificates here...

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Mandatory Cross-Origin Isolation Headers
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
    }
}

Caddy Configuration

retro.yourdomain.com {
    reverse_proxy 127.0.0.1:8080 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
    }
    header {
        Cross-Origin-Opener-Policy "same-origin"
        Cross-Origin-Embedder-Policy "require-corp"
    }
}

Storage & Compression Best Practices

  • PlayStation 1 (.chd): Convert all multi-bin PS1 games to single CHD files using chdman createcd -i game.cue -o game.chd. This reduces file sizes by ~50% and removes multi-track indexing errors in browser emulators.
  • Save State Syncing: RomM stores emulator save states inside the resources/saves/ volume. Back up this specific directory using Borg or Restic to preserve in-game progress across container recreations.