Most retro gaming setups fall apart the moment you own more than one device. A Steam Deck running EmuDeck, a Windows PC running RetroArch, a phone running a random APK — three separate ROM folders, three separate save files, and zero way to tell which copy of Chrono Trigger is the one you actually played past the Ocean Palace. RomM (short for ROM Manager) fixes that by putting your entire library behind one self-hosted web server: scan once, get real box art and metadata from IGDB, ScreenScraper, MobyGames, and RetroAchievements, then stream or download games to any device on your network.
This tutorial walks through a full RomM install on a home server or NAS using Docker, from the docker-compose file to metadata provider API keys to the common failure points that trip up first-time self-hosters. By the end you will have a working library manager reachable from your browser, phone, and Steam Deck, with a project that GitHub currently lists at version 4.6.1 (with a 4.9.2 build line active as of June 2026) and support for more than 400 platforms.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is RomM and Why Self-Host Your ROM Library
RomM is a free, open-source, self-hosted ROM manager and player. Instead of an app installed on one machine, it runs as a web service — most commonly inside a Docker container on a home server, NAS, or mini PC — and exposes a browser-based library that any device on your network can reach. The project describes itself as letting users “scan, enrich, browse and play” a game collection through a “clean and responsive interface,” and its documentation lists support for roughly 400 platforms once metadata providers are configured.
The core problem RomM solves is fragmentation. Emulation frontends like EmuDeck, Batocera, and RetroArch are excellent at running games on a specific device, but none of them centralize a library across devices. RomM sits underneath all of them: it scans your ROM folders, pulls metadata from IGDB, ScreenScraper.fr, MobyGames, and RetroAchievements, and serves the result as a searchable, filterable web catalog with cover art, screenshots, and (for many systems) in-browser play. You still use EmuDeck on your Steam Deck or RetroArch on your PC to actually run games at full performance — RomM is the shared library and metadata layer sitting behind them, not a replacement for either.
Two details matter for anyone weighing this against a simpler folder-based setup. First, RomM pulls from multiple metadata sources with a configurable priority order (IGDB first by default, then MobyGames, ScreenScraper, RetroAchievements, LaunchBox, and a handful of others), so mismatched or missing box art becomes rare even for obscure regional releases. Second, because it runs server-side, a properly configured RomM instance stays in sync no matter how many devices you add — a laptop, a phone, or a second Steam Deck all see the identical library without re-scraping.
How RomM’s Metadata Priority System Actually Works
Understanding the provider chain before your first scan saves a lot of manual cleanup later. RomM does not query every configured provider for every file and then let you pick a favorite result — it works down a fixed priority list, stopping at the first confident match. The documented default order is IGDB, MobyGames, ScreenScraper, RetroAchievements, LaunchBox, ES-DE’s gamelist.xml format, Hasheous, Flashpoint, and finally HowLongToBeat for playtime estimates only.
That ordering has practical consequences. IGDB, sitting at the top, covers roughly 200-plus systems and tends to win for mainstream console and PC titles, but it is comparatively weak on obscure regional variants, unlicensed cartridges, and older Japanese computer platforms. ScreenScraper, by contrast, was built by and for the European retro community and frequently has better coverage for exactly those edge cases — which is why setting it up in addition to IGDB, rather than instead of it, is the configuration most experienced self-hosters land on. Hasheous adds a third layer that works by file hash rather than filename, which matters if your collection includes files that were renamed at some point and no longer carry clean naming metadata.
You can reorder this priority list in RomM’s configuration file if a particular provider consistently outperforms another for your specific collection — a library heavy on Japanese PC-98 titles, for instance, might benefit from moving ScreenScraper above IGDB. Most users never need to touch this, but knowing it is configurable explains why two RomM installs with identical ROM files can occasionally produce slightly different box art results if their provider priority or account regions differ.
Prerequisites: What You Need Before You Start
RomM is lightweight compared to a full media server, but it still expects a few things to be in place before installation. Gather these first so you are not troubleshooting mid-setup.
- A home server, NAS, or mini PC running Docker — Unraid, TrueNAS Scale, Synology DSM 7.2+, Proxmox with a Docker/LXC guest, or a plain Ubuntu Server 22.04/24.04 box all work.
- Docker Engine 24+ and Docker Compose v2 installed and confirmed working (
docker compose versionshould return a version string, not an error). - At least 2 CPU cores and 4 GB of RAM allocated to the container host — RomM’s own documentation frames it as suitable for modest NAS-class hardware, with actual load coming from concurrent scraping and playback rather than idle library browsing.
- Storage sized for your ROM collection plus metadata — the app and database are small (well under 1 GB combined), but box art, screenshots, and manuals for a few thousand games can add several GB on top of the ROMs themselves.
- A MariaDB or PostgreSQL database — RomM needs a real database backend rather than a flat file; this guide uses MariaDB via linuxserver.io’s image since it is the more commonly documented path for home installs.
- Free accounts with at least IGDB (via Twitch developer account) and ScreenScraper.fr — both are required to unlock full metadata scraping; RomM works without them but returns far thinner box art and descriptions.
- Your existing ROM files, organized by system — dumped from cartridges, discs, or carts you own. RomM does not supply ROMs or BIOS files and never will; it is a library manager, not a source of game files.
- Basic familiarity with a terminal — this guide uses SSH and docker-compose commands throughout. If you have never opened a terminal on your NAS, budget extra time for step 2.
A note on legality before going further: RomM organizes and plays whatever files you give it, but it does not make those files legal to possess. Dumping ROMs from cartridges or discs you personally own is the only safe path in most jurisdictions; downloading ROMs for games you do not own is copyright infringement almost everywhere, self-hosted server or not. This tutorial assumes you are working with a legally dumped personal collection.
Step 1: Plan Your Folder Structure
Before touching Docker, decide where ROMs, BIOS files, and RomM’s own configuration will live on disk. RomM expects three logical locations mapped into the container: a ROMs directory (subdivided by platform), a resources/media directory for scraped art, and a config directory for the app itself. On a NAS this typically means a dedicated share; on a plain Linux box it means a directory tree under /srv or /opt.
Create the base structure over SSH:
sudo mkdir -p /srv/romm/{config,roms,resources,db}
sudo mkdir -p /srv/romm/roms/{nes,snes,n64,gba,nds,psx,ps2,psp,gc,wii,segamd,saturn,dreamcast,arcade}
sudo chown -R 1000:1000 /srv/romm
sudo chmod -R 755 /srv/romm
The platform subfolder names matter — RomM identifies systems partly by folder naming convention during its scan, so using recognizable slugs (snes, not super-nintendo-games) saves time later. The official docs list the expected folder-name-to-platform mapping; when in doubt, match what EmuDeck or Batocera already uses, since those conventions overlap heavily with RomM’s own.
Step 2: Install Docker and Docker Compose
Skip this step if your NAS OS (Synology DSM, TrueNAS Scale, Unraid) already ships Docker through its own app center. For a bare Linux server, install Docker’s official convenience script:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker
docker compose version
The last command should print something like Docker Compose version v2.x.x. If it instead returns “command not found,” your Docker install predates the Compose v2 plugin — reinstall using the official Docker convenience script rather than an older distro package, since many Linux repositories still ship the deprecated standalone docker-compose binary.
Step 3: Register for Metadata Provider API Keys
RomM’s metadata quality is directly proportional to how many providers you configure. The default priority order is IGDB, then MobyGames, ScreenScraper, RetroAchievements, LaunchBox, ES-DE gamelist.xml, Hasheous, Flashpoint, and HowLongToBeat — each optional, each additive. At minimum, set up IGDB and ScreenScraper before your first scan; skipping this step is the single most common reason people report “RomM found my games but everything looks blank.”
- IGDB — requires a free Twitch developer account. Register an application at the Twitch developer console to get a Client ID and Client Secret; IGDB authenticates through Twitch’s OAuth system, which trips up first-timers who go looking for an “IGDB API key” that does not exist as such.
- ScreenScraper.fr — create a free account directly on ScreenScraper. This provider is particularly strong for European and Japanese regional releases that IGDB often lacks.
- MobyGames — request an API key through a free MobyGames account; approval can take a day or two, so request it early if you want it available for your first scan.
- RetroAchievements — generate a Web API key from your account settings on the RetroAchievements site, following the format documented at RetroAchievements’ API docs. This unlocks per-user achievement tracking inside RomM itself.
Write down every credential as you generate it. The docker-compose file in the next step expects all of them as environment variables, and hunting for a forgotten API key after the container is already running is a needless detour.
Step 4: Write the Docker Compose File
With folders ready and credentials in hand, create the compose file that defines both RomM and its MariaDB database. Save this as /srv/romm/docker-compose.yml:
services:
romm-db:
image: linuxserver/mariadb:latest
container_name: romm-db
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- MYSQL_DATABASE=romm
- MYSQL_USER=romm
- MYSQL_PASSWORD=change_this_password
- MYSQL_ROOT_PASSWORD=change_this_root_password
volumes:
- /srv/romm/db:/config
romm:
image: rommapp/romm:latest
container_name: romm
restart: unless-stopped
depends_on:
- romm-db
ports:
- "8080:8080"
environment:
- DB_HOST=romm-db
- DB_PORT=3306
- DB_NAME=romm
- DB_USER=romm
- DB_PASSWD=change_this_password
- ROMM_AUTH_SECRET_KEY=generate_a_long_random_string_here
- IGDB_CLIENT_ID=your_twitch_client_id
- IGDB_CLIENT_SECRET=your_twitch_client_secret
- SCREENSCRAPER_USER=your_screenscraper_username
- SCREENSCRAPER_PASSWORD=your_screenscraper_password
- MOBYGAMES_API_KEY=your_mobygames_key
- RETROACHIEVEMENTS_API_KEY=your_ra_web_api_key
volumes:
- /srv/romm/config:/romm/config
- /srv/romm/resources:/romm/resources
- /srv/romm/roms:/romm/library/roms
- /srv/romm/roms/bios:/romm/library/bios
Generate the ROMM_AUTH_SECRET_KEY value rather than leaving it blank — a long random string works, and openssl produces one instantly:
openssl rand -hex 32
Paste that output in place of generate_a_long_random_string_here. Replace both database passwords with your own strong values, and swap every provider placeholder for the credentials gathered in step 3. Skipping a provider is fine — just delete that line rather than leaving a blank value, since an empty variable can cause RomM to log repeated authentication failures on startup.
Step 5: Launch the Containers
From inside /srv/romm, bring the stack up:
cd /srv/romm
docker compose up -d
docker compose logs -f romm
The log output should show the database connection succeeding, followed by RomM’s web server starting on port 8080. First boot can take 30–90 seconds while the app initializes its database schema — do not restart the container if the logs pause briefly during this phase. Once you see a line indicating the server is listening, open a browser to http://your-server-ip:8080 and you should land on RomM’s initial account-creation screen.
Create your admin account here. This is the account that owns library-wide settings and the primary RetroAchievements integration; additional user accounts (covered in step 9) get more limited permissions by default.
Step 6: Copy ROMs and BIOS Files Into Place
With the container running, move your existing ROM collection into the folder structure created in step 1. Over the network, this typically means an SMB or SFTP transfer directly into /srv/romm/roms/<platform>/. BIOS files belong in the dedicated bios subfolder mapped in the compose file — RomM checks for correctly named BIOS files per emulator core the same way EmuDeck and Batocera do, and a missing BIOS file will not stop a game from appearing in the library, only from launching correctly where in-browser play is used.
# Example: copying a SNES collection from a Windows PC to the NAS share
robocopy "D:\ROMs\SNES" "\\your-nas\romm\roms\snes" /E
# Example: same task from Linux/macOS via rsync over SSH
rsync -avz --progress ./roms/snes/ user@your-server:/srv/romm/roms/snes/
Keep file naming reasonably clean — no-intro or redump naming conventions (the same standard EmuDeck and Batocera expect) give metadata scrapers the best chance of an exact match. A file named Chrono Trigger (USA).sfc will match instantly; a file named ct_final_v2_FIXED.sfc will not, and you will end up manually correcting the match later.
Step 7: Run Your First Library Scan
Inside RomM’s web interface, trigger a full library scan from the admin panel. This is the step where the metadata providers configured in step 3 actually get used — RomM works through the provider priority order (IGDB first by default) attempting to match each file, falling back to the next provider when a match fails.
A scan of a few thousand ROMs across a dozen platforms typically takes anywhere from several minutes to over an hour, depending on how many files need external API calls versus how many hit ScreenScraper or MobyGames’ rate limits. Larger collections benefit from running the first scan overnight rather than watching a progress bar. Subsequent scans are far faster since RomM only re-processes new or changed files.
Once complete, browse to the library view. Each platform should show as a shelf with cover art, and clicking into a game should surface a description, screenshots, and (for supported systems) a play button that launches an in-browser emulator session without needing a separate emulator installed on the viewing device.
Step 8: Set Up a Reverse Proxy for Remote Access
Port 8080 over plain HTTP works fine on a local network but is not something to expose directly to the internet. If you want to reach your library from outside your home — on a phone over mobile data, for instance — put RomM behind a reverse proxy that handles HTTPS. Caddy is the simplest option for a first setup because it automates certificate issuance.
# /etc/caddy/Caddyfile
romm.yourdomain.com {
reverse_proxy localhost:8080
}
Reload Caddy and it automatically requests and renews a TLS certificate for the domain, no manual certbot commands required. If you already run nginx for other services, a standard reverse-proxy server block pointing at 127.0.0.1:8080 works identically — just remember to set proxy_set_header Upgrade and Connection headers if you want WebSocket-based features like live scan progress to work correctly through the proxy.
Whichever proxy you choose, do not skip authentication. RomM’s own login handles user access, but pairing it with a proxy-level basic auth layer, a VPN like Tailscale, or at minimum strict firewall rules on the exposed port meaningfully reduces the attack surface of a server holding a large personal media library.
Step 9: Configure Multi-User Access
If more than one person in your household will use the library, add accounts from the admin panel rather than sharing the primary login. Each user can maintain their own RetroAchievements API key, meaning achievement progress tracked through RomM stays tied to the correct individual account rather than blending into a single shared record. This matters in particular for households where kids and adults share the same server but should not share achievement history or play-state tracking.
Admin accounts retain access to server-wide settings — metadata provider configuration, library rescans, and user management — while standard accounts are scoped to browsing and playing. There is no granular per-platform permission system as of the current release line, so if you need to restrict specific users from specific content, the practical workaround is separate library folders and separate scan scopes rather than in-app permission rules.
Step 10: Connect RomM to Your Existing Emulation Setup
RomM is not meant to replace the emulator running on your Steam Deck or gaming PC — it is meant to feed them a clean, centrally managed library. The most direct integration path runs through the metadata formats RomM already speaks: it can both read and export data compatible with LaunchBox databases and ES-DE’s gamelist.xml format, which is the same metadata language used by frontends built on EmulationStation, including setups built with EmuDeck and Batocera.
In practice, most users run RomM as the source of truth for ROM files and metadata, then either download files on demand to a device’s local storage before playing (best for handhelds like a Steam Deck) or use RomM’s browser-based play directly for quick sessions on a laptop or phone where installing a full emulator is not worth the effort. If your primary rig already runs RetroArch, you can point RetroArch’s content directory at the same network share RomM manages, giving you one physical copy of every ROM shared across both systems rather than duplicated libraries drifting out of sync.
Step 11: Set Up Automatic Backups
Your database holds every scraped match, every custom edit, and your entire user list — losing it means re-scanning and re-matching your whole library from scratch. Back up the MariaDB volume and the RomM config directory on a schedule, separate from whatever backup routine already covers your raw ROM files.
# Simple cron-driven backup of the database container
docker exec romm-db mysqldump -u romm -pchange_this_password romm > /srv/romm/backups/romm-$(date +%F).sql
# Rotate backups older than 14 days
find /srv/romm/backups -name "*.sql" -mtime +14 -delete
Add this as a cron job (crontab -e) to run nightly, and confirm at least once that a restore actually works by spinning up a throwaway container pointed at a restored dump. A backup you have never tested restoring is a backup you do not actually have.
Step 12: Keep RomM Updated
RomM ships frequent point releases — the project’s GitHub tags list version 4.6.1 as a January 2026 release, with the broader 4.x/4.9.x line continuing to add metadata provider refinements and platform coverage through mid-2026. Because the image is pulled by tag, updating is a two-command operation:
cd /srv/romm
docker compose pull
docker compose up -d
Check the release notes on the project’s GitHub page before updating a production instance with a large library — while most releases are backward compatible, database migrations occasionally accompany major version bumps, and running a fresh database backup immediately before pulling a new image costs nothing and avoids a genuinely bad afternoon if a migration goes sideways.
Common Pitfalls When Setting Up RomM
Most first-time RomM installs stumble on the same handful of mistakes. Working through these before you start saves a repeat scan later.
- Leaving metadata provider variables blank instead of deleting them. An empty
IGDB_CLIENT_ID=line behaves differently than removing the line entirely, and can cause repeated failed authentication attempts logged on every scan. - Wrong file ownership on mapped volumes. If
PUID/PGIDon the database container do not match the ownership of the host folders, RomM may fail to write scraped images even though the scan itself appears to succeed. - Non-standard ROM naming. Files without region tags or with heavily modified filenames frequently fail to auto-match; a manual metadata correction pass is often unavoidable for a messy legacy collection.
- Skipping the database backup before an update. Point releases are generally safe, but skipping backups before a major version jump turns a routine update into a real risk.
- Exposing port 8080 directly to the internet without a reverse proxy or VPN. Plain HTTP with no additional layer is not a safe way to publish a home server, regardless of how strong the RomM login password is.
Example RomM Output: What a Configured Library Looks Like
After a completed scan with IGDB and ScreenScraper both configured, a typical platform shelf in RomM’s web UI shows each title with cover art, a release year badge, and a match confidence indicator. Selecting a game opens a detail view with a synopsis pulled from IGDB, screenshots from ScreenScraper, and (where RetroAchievements has data for that title) an achievement completion percentage tied to the logged-in user’s account. Games without a confident auto-match display a generic placeholder and an “unmatched” flag in the admin scan report, making cleanup a filtered list rather than a manual hunt through the entire catalog.
| Component | Recommended Setting | Why It Matters |
|---|---|---|
| Database engine | MariaDB (linuxserver/mariadb) | More widely documented for RomM home installs than PostgreSQL, easier troubleshooting |
| Reverse proxy | Caddy (auto-HTTPS) or nginx | Required before exposing the library outside the local network |
| Metadata priority | IGDB → MobyGames → ScreenScraper → RetroAchievements | Default order documented by the project; covers the widest platform range first |
| ROM naming convention | No-Intro / Redump style, with region tags | Sharply increases automatic metadata match rate |
| Minimum RAM | 4 GB allocated to the container host | Comfortable headroom for concurrent scraping and in-browser play sessions |
RomM vs Alternatives: Where It Fits
RomM occupies a specific niche: a web-based, self-hosted, multi-user library manager rather than a device-local emulation frontend. That distinction matters when deciding whether it is the right tool for your setup, or whether you actually need something else entirely.
| Tool | Type | Best For | Runs Games Directly? |
|---|---|---|---|
| RomM | Self-hosted web library manager | Centralizing metadata and files across multiple devices | Yes, in-browser for supported systems |
| LaunchBox | Windows desktop frontend | Single-PC library with polished local UI | Yes, launches local emulators |
| EmuDeck | Device-local emulator installer/configurator | Setting up Steam Deck or handheld emulation from scratch | Yes, configures and launches emulators on-device |
| Batocera | Standalone Linux OS for dedicated emulation boxes | Turning a spare PC into a dedicated retro console | Yes, boots directly into EmulationStation |
The practical takeaway: RomM and a device-local tool like EmuDeck or RetroDECK are not competitors, they are complementary layers. RomM manages the master copy of your library and its metadata; the device-local tool handles the actual emulation performance on whatever hardware you are playing on that day.
Sizing Your Server: Real-World Storage Examples
The abstract “enough disk space” guidance in the prerequisites section is more useful with concrete numbers attached. Storage needs for a RomM install scale almost entirely with how many disc-based systems you include, since cartridge-era ROMs are tiny by comparison to PS2, GameCube, or Dreamcast disc images.
| Collection Profile | Approximate ROM Size | Metadata/Art Overhead | Recommended Free Space |
|---|---|---|---|
| Cartridge-only (NES–GBA, Genesis, SNES) | 5–15 GB | 1–2 GB | 25 GB |
| Mixed cartridge + early disc (adds PSX, Saturn) | 40–100 GB | 3–5 GB | 150 GB |
| Full retro + sixth-gen (adds PS2, GameCube, Dreamcast) | 300–800 GB | 5–10 GB | 1 TB |
| Everything, including PS3/Wii U-class systems | 1–4 TB+ | 10–20 GB | 4–6 TB |
These figures assume a personal collection built from your own owned media rather than a maximal every-region dump. If you are starting from a modest cartridge-only library, a single NAS bay or even a large external drive comfortably covers both storage and headroom for a few years of growth. Once GameCube, PS2, or Dreamcast enter the picture, plan for a proper multi-drive NAS array rather than a single external disk, since RomM’s scan performance and your own browsing experience both degrade noticeably on slow single-disk storage under concurrent access.
Security Hardening for a Self-Hosted Game Library
A RomM server is, functionally, a small web application with a database and a login system sitting on your home network — the same security basics that apply to any self-hosted service apply here. Beyond the reverse proxy and VPN advice already covered in step 8, a few additional habits meaningfully reduce risk for a library server that may end up reachable from outside your home.
- Rotate the auto-generated
ROMM_AUTH_SECRET_KEYif it was ever committed to a public repository or shared screenshot. This key signs session tokens; treat it with the same care as a database password. - Disable public account registration once your household accounts are created, so the server does not silently accept new sign-ups from anyone who reaches the login page.
- Keep the reverse proxy’s access logs and check them periodically for repeated failed login attempts, particularly if the instance is reachable outside your LAN.
- Isolate the RomM stack on its own Docker network rather than running it on a shared bridge network with unrelated containers, limiting what a compromised container could reach.
- Apply the same update discipline described in step 12 to the host OS and Docker Engine itself, not just the RomM image — an outdated Docker Engine is a more common attack surface than the application running on top of it.
Advanced Tips for a Better RomM Setup
A handful of adjustments turn a working RomM install into one that stays reliable as your library grows past a few thousand titles.
- Run incremental scans, not full rescans, after the initial import. Once the first pass completes, scheduling nightly incremental scans (new files only) keeps metadata current without hammering provider rate limits.
- Use SteamGridDB as a supplementary art source for platforms where IGDB and ScreenScraper cover art looks inconsistent — many home-server setups pair it in for a more uniform grid view.
- Pin your RomM image tag instead of using
latestin production. This gives you control over exactly when a database migration happens, rather than inheriting it silently on the nextdocker compose pull. - Separate your BIOS folder permissions from your general ROMs folder. BIOS files are small in number but critical to get right; isolating them makes permission debugging faster when a specific system refuses to launch.
- Monitor container resource usage during your first full scan. If scraping spikes CPU or memory beyond what your NAS comfortably handles, throttle by scanning platform-by-platform instead of the entire library in one pass.
Troubleshooting: 8 Common RomM Problems and Fixes
1. Container starts but the web UI shows a database connection error. Confirm the romm-db container is actually running (docker compose ps) and that DB_HOST in the RomM service matches the database service’s container name exactly. A typo here is the most common cause of this specific error.
2. Scan completes but most games show no cover art. This almost always means metadata provider credentials were missing or invalid at scan time. Verify your IGDB and ScreenScraper credentials in the compose file, restart the container, and re-run the scan rather than waiting for it to self-correct.
3. Games appear in the library but will not launch in-browser. Check that the required BIOS file for that platform is present in the mapped bios directory with the correct filename. In-browser play for BIOS-dependent systems fails silently in some builds rather than showing a clear error.
4. Reverse proxy returns a 502 error. This typically means the proxy is pointed at the wrong port or the RomM container is not yet finished starting. Check docker compose logs romm for a “listening” confirmation before assuming the proxy configuration itself is broken.
5. RetroAchievements data never appears for a logged-in user. RetroAchievements integration is tied to the individual user account’s own API key, not the server-wide credential. Confirm the affected user has entered their personal RetroAchievements Web API key in their account settings, not just the admin account.
6. Scan runs for hours and never finishes on a large collection. Very large libraries (tens of thousands of files) can hit provider rate limits, particularly on ScreenScraper’s free tier. Break the initial import into per-platform scans run across several nights rather than one continuous pass.
7. Container restarts in a loop after an update. Check the logs for a database migration error. If a migration failed partway, restore the pre-update database backup taken in step 11 and hold the image at the previous working tag until you can investigate further.
8. Files copied to the ROMs folder never appear after a scan. Confirm the folder permissions match the container’s PUID/PGID — files copied in as root or a different user than the container expects are sometimes invisible to the scan process even though they are physically present on disk.
Complete Working Project: Full Docker Compose Reference
For reference, here is the complete stack combining the compose file, folder structure, and backup script covered across this tutorial into a single ready-to-adapt project.
# 1. Folder structure
mkdir -p /srv/romm/{config,resources,db,backups}
mkdir -p /srv/romm/roms/{nes,snes,n64,gba,nds,psx,ps2,psp,gc,wii,segamd,saturn,dreamcast,arcade,bios}
chown -R 1000:1000 /srv/romm
# 2. docker-compose.yml (place in /srv/romm/)
# services:
# romm-db: (MariaDB backend)
# romm: (application, port 8080)
# — see full listing in Step 4 above
# 3. Launch
cd /srv/romm && docker compose up -d
# 4. Nightly backup cron entry (crontab -e)
0 3 * * * docker exec romm-db mysqldump -u romm -pchange_this_password romm > /srv/romm/backups/romm-$(date +\%F).sql
# 5. Update routine (run manually or via a maintenance window)
cd /srv/romm && docker compose pull && docker compose up -d
This gives you a self-contained, backed-up, update-friendly RomM instance that any device on your network — and, once the reverse proxy from step 8 is in place, any device outside it — can reach as a single source of truth for your retro library.
Frequently Asked Questions
Is RomM free to use?
Yes. RomM is free and open source, distributed through GitHub with no license fee for personal use.
Do I need a NAS to run RomM?
No. Any machine capable of running Docker works, including a spare desktop, a Raspberry Pi 4 or newer with sufficient RAM, or a cloud VPS, though a NAS or home server is the most common choice because it is already running continuously.
Can RomM replace EmuDeck or Batocera entirely?
Not directly. RomM manages your library and metadata centrally, but device-local tools like EmuDeck and Batocera handle the actual emulator configuration and performance tuning on a specific handheld or PC. Most users run both together.
Does RomM provide ROMs or BIOS files?
No. RomM only organizes, scrapes metadata for, and plays files you already have. You are responsible for legally obtaining any ROM or BIOS file you add to the library.
Which metadata provider should I set up first?
IGDB, since it is the default top-priority provider and covers the broadest range of mainstream platforms. Add ScreenScraper next for stronger regional and retro-specific coverage.
Can multiple people use the same RomM server with separate accounts?
Yes. RomM supports multiple user accounts, each with its own RetroAchievements API key and login, though granular per-platform permissions are limited in the current release line.
Is it safe to expose my RomM server to the internet?
Only behind a reverse proxy with HTTPS, and ideally behind additional protection such as a VPN like Tailscale or proxy-level authentication. Exposing port 8080 directly over plain HTTP is not recommended.
How often should I update RomM?
Point releases can be applied as they appear with minimal risk. For major version jumps, back up your database first, since occasional releases include schema migrations.


