# ROMM - ROM Manager for Game Libraries # Domain: ${SUBDOMAIN}.${DOMAIN} # Database: MariaDB + Redis # RAM: ~300MB (mem_limit: 1280M total — romm 768M + mariadb 384M + redis 128M) | Pi-compatible: No (MariaDB + heavy) # # Environment variables: # DOMAIN - Your domain (e.g., demo-felhom.eu) # HDD_PATH - Drive namespace root (appdata lives here) # USERDATA_PATH - Ügyfél-tartalom gyökér (/userdata) # DB_PASSWORD - MariaDB user password (auto-generated) # MYSQL_ROOT_PASSWORD - MariaDB root password (auto-generated) # ROMM_AUTH_SECRET_KEY - Auth secret (auto-generated) # # Storage layout (felhom userdata convention): # ROM library → ${USERDATA_PATH}/roms (browsable — drop ROMs here via FileBrowser) # Cover art etc → ${HDD_PATH}/appdata/romm/resources (app-internal, NOT browsable) # App config → romm_config (named volume, NVMe) # MariaDB data → romm_db_data (named volume, NVMe) # Redis data → romm_redis_data (named volume, NVMe) # # First-time setup: # Default login: admin / admin — change immediately! services: romm: image: rommapp/romm:5.3.1 container_name: romm restart: unless-stopped depends_on: romm-db: condition: service_healthy romm-redis: condition: service_healthy entrypoint: ["/bin/sh", "-c"] command: - | if [ ! -f /romm/config/config.yml ]; then echo "Creating default config.yml..." cat > /romm/config/config.yml << 'CONF' exclude: platforms: [] roms: [] system: log_level: INFO CONF fi exec /docker-entrypoint.sh /init environment: - ROMM_AUTH_SECRET_KEY=${ROMM_AUTH_SECRET_KEY} - DB_PASSWD=${DB_PASSWORD} - DB_HOST=romm-db - DB_PORT=3306 - DB_NAME=romm - DB_USER=romm - REDIS_HOST=romm-redis - REDIS_PORT=6379 - ROMM_PORT=8080 # 2, not the image's default of 4. MEASURED on demo-hp 2026-09-22 (R-635): each warm # uvicorn worker holds ~216 MiB, so four of them plus the master reach ~882 MiB and the # container was OOM-killed at both 512M and 768M — 37 worker SIGKILLs in five minutes, # ~500% CPU, host load 5.2 while otherwise idle. Four workers is a SERVER default; this # is one household on one small box. Two workers measure ~450 MiB and fit 768M with # real headroom. `/init:143` reads this variable: --workers "${WEB_SERVER_CONCURRENCY:-4}". - WEB_SERVER_CONCURRENCY=2 - IGDB_CLIENT_ID=${IGDB_CLIENT_ID:-} - IGDB_CLIENT_SECRET=${IGDB_CLIENT_SECRET:-} - STEAMGRIDDB_API_KEY=${STEAMGRIDDB_API_KEY:-} - SCREENSCRAPER_USER=${SCREENSCRAPER_USER:-} - SCREENSCRAPER_PASSWORD=${SCREENSCRAPER_PASSWORD:-} - MOBYGAMES_API_KEY=${MOBYGAMES_API_KEY:-} - TZ=Europe/Budapest volumes: - ${USERDATA_PATH}/roms:/romm/library - ${HDD_PATH}/appdata/romm/resources:/romm/resources - romm_config:/romm/config networks: - traefik-public - romm-internal deploy: resources: limits: # 768M, not 512M: romm 5.3.0 does not fit in 512M. Measured on demo-hp 2026-09-22 — # OOMKilled true, 4,530 gunicorn worker SIGKILLs in six hours, ~500% CPU in a permanent # restart storm, while the web front end still answered 200 so the update read `done` # (R-635). 5.0.0 did fit; the version moved and the limit did not. memory: 768M healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8080/"] interval: 30s timeout: 10s retries: 3 start_period: 60s labels: - "traefik.enable=true" - "traefik.http.routers.romm.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" # R-753: this app reads the LEFTMOST X-Forwarded-For entry, which a stranger writes once traefik keeps the # tunnel's chain — so its chain is removed here (it then reads traefik's X-Real-Ip or peer; never forgeable). - "traefik.http.middlewares.romm-xff.headers.customrequestheaders.X-Forwarded-For=" - "traefik.http.routers.romm.middlewares=romm-xff" - "traefik.http.routers.romm.entrypoints=websecure" - "traefik.http.routers.romm.tls=true" - "traefik.http.routers.romm.tls.certresolver=letsencrypt" - "traefik.http.services.romm.loadbalancer.server.port=8080" romm-db: image: mariadb:11.8 container_name: romm-db restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE=romm - MYSQL_USER=romm - MYSQL_PASSWORD=${DB_PASSWORD} - TZ=Europe/Budapest # MARIADB_AUTO_UPGRADE: on a MAJOR engine move the engine converts its own datadir (~7 s on a # small DB, backs its system tables up first). Operator ruling 2026-09-13 on # felhom.eu/documentation/audits/SPIKE-r459-mariadb-upgrade-2026-09-06.md. Inert until a # major moves — and none may, until Slice 4 (R-448) ships: see CLAUDE.md, engine-major rule. - MARIADB_AUTO_UPGRADE=1 volumes: - romm_db_data:/var/lib/mysql networks: - romm-internal deploy: resources: limits: memory: 384M healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 5 start_period: 30s romm-redis: image: redis:7-alpine container_name: romm-redis restart: unless-stopped command: redis-server --appendonly yes environment: - TZ=Europe/Budapest volumes: - romm_redis_data:/data networks: - romm-internal deploy: resources: limits: memory: 128M healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3 volumes: romm_config: romm_db_data: romm_redis_data: networks: traefik-public: external: true romm-internal: