本文使用的 Docker 镜像
EmulatorJS 是什么
LinuxServer.io 维护的 linuxserver/emulatorjs 把复古游戏收成一个网页。管理界面用来扫描游戏文件、匹配封面和生成菜单,游戏页面在访问者的浏览器里运行,电脑、手机或装了 Chromium 浏览器的设备打开网址就能玩。
与 emulatorjs.com 上持续开发的 EmulatorJS 不同,这个容器是 LinuxServer 做的自托管前端,模拟核心主要来自 Libretro。LinuxServer 已于 2025 年 6 月 11 日停止维护该镜像,不再提供支持,也不再发布新版本。
EmulatorJS 能做什么
- 在浏览器里玩游戏:打开游戏页面后,用键盘、鼠标或手柄选择并运行游戏。
- 按主机分目录:红白机、超任、街机等各自一个目录,扫描后写入菜单。
- 带上封面和视频:已经识别的游戏可以下载标志、背景和演示视频。
- 游戏在浏览器里跑:模拟在访问者的网页里执行,容器负责菜单和文件。
- 管理界面和游戏页面分开:一边扫描、下封面,一边在另一个页面里选游戏。
为什么要使用 EmulatorJS
每换一台电脑都要再装模拟器,手机上也进不去。游戏若只放在别人的网站上,网站关掉就打不开,文件也不在自己的磁盘里。
这个容器把扫描、封面和菜单留在宿主机上,打开网页就能玩。适合已经有自己的游戏文件、想在局域网里用浏览器玩的人。
镜像已经停止更新,适合收藏不再增加、只想把现有文件放进浏览器的情况。还要持续收到新版本时,见「常见问题」里的替代项目。
使用 Docker 部署 EmulatorJS
用 Docker 跑这个前端,宿主机不用再单独安装 Node.js、Nginx 和 IPFS。开始之前准备好 Docker,以及你有权使用的游戏文件。
接下来用 Docker Compose 写好数据目录和端口,启动服务,再在浏览器里下载默认文件、放入游戏并打开游戏页面。
环境要求
本文命令使用 linuxserver/emulatorjs:1.9.2。这是停止维护前的最后版本,标签记录的推送时间是 2025-06-09。
| 项目 | 建议 |
|---|---|
| 系统 | Linux(正文命令按 bash)。macOS 把 /www/wwwroot/emulatorjs 换成 ~/docker/emulatorjs。Windows 安装 Docker Desktop 后,在能执行 bash 的终端里运行下文命令 |
| Docker | 已安装 Docker Engine 与 Compose V2(docker compose) |
| 架构 | linux/amd64、linux/arm64。armhf 已在 2023 年停止提供 |
| 磁盘 | 标签记录里 amd64 压缩体积约 272 MB,arm64 约 269 MB。游戏文件、封面和 IPFS 数据另算 |
| 端口 | 宿主机 13300 → 容器 3000(管理界面)。宿主机 8080 → 容器 80(游戏页面) |
| 工作目录 | /www/wwwroot/emulatorjs |
| 数据目录 | config/ 对应容器内 /config,只放手柄和显示档案。游戏、封面、菜单 JSON 和 IPFS 都在 data/,菜单文件是 data/config/arcade.json 这一类,不要放进 config/ |
| 账号 | 管理界面没有登录,只在局域网使用 |
text浏览器 ├─ 宿主机 13300 → 容器 3000 管理界面:扫描、封面、菜单 └─ 宿主机 8080 → 容器 80 游戏页面:选游戏、游玩 │ ▼ ./config → /config 手柄与显示档案 ./data → /data 游戏、封面、菜单 JSON、IPFS
游戏在打开网页的那台设备上运行。管理页写明,非光盘主机在手机上一般可以玩;完整光盘游戏需要较大内存,手机和游戏主机浏览器通常带不动。第一次下载默认封面时,容器要能访问外网。
管理界面没有登录。13300 和 8080 只给局域网使用。
bashdocker --version docker compose version
Linux 未装 Docker 可使用轩辕镜像一键安装脚本:
bashbash <(wget -qO- https://get.xuanyuan.cloud/docker.sh)
备用地址:
bashbash <(wget -qO- https://get.xuanyuan.me/docker.sh)
更多见 轩辕镜像使用手册。
Windows 和 Mac 从 Docker Desktop 安装,安装后启动 Docker Desktop。下文的 cat > compose.yaml <<'EOF' 是 bash 写法。PowerShell 不能直接粘贴这一段,请改用 Git Bash、WSL,或在 Linux 上执行。
标签怎么选
标签列表 上,1.9.2、version-1.9.2 和 1.9.2-ls173 是 2025-06-09 的同一份镜像。拉取输出里的 Digest 是这份多架构索引,不要拿它和单架构标签的摘要逐字对比。amd64-1.9.2 和 arm64v8-1.9.2 各只含一种架构,只有必须指定架构时才用。
LinuxServer 的停止维护说明写明,弃用之后不能再拉取 latest,以及 develop、nightly 这类分支标签,需要使用具体版本。下面的命令使用 1.9.2。
| 标签 | 说明 | 是否写入本文命令 |
|---|---|---|
1.9.2 | 2025-06-09 这份具体版本,含 amd64 与 arm64 | 是 |
version-1.9.2 | 与 1.9.2 同一份镜像的另一种写法 | 否 |
1.9.2-ls173 | 同一次 CI 构建 | 否 |
amd64-1.9.2 / arm64v8-1.9.2 | 只含一种架构 | 必须指定架构时再用 |
latest / amd64-latest / arm64v8-latest | 浮动标签 | 否 |
拉取镜像
用 轩辕镜像 加速拉取:
bashdocker pull docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2
Ubuntu 24.04 实测:
text1.9.2: Pulling from linuxserver/emulatorjs d8e8df0cf819: Pull complete 293fa8c1bfff: Pull complete c0f8eff906b8: Pull complete 2a6a7e2d8be8: Pull complete bffbd6212769: Pull complete 0e152a8e12f7: Pull complete 75dee0b5a4da: Pull complete 9f8512f323d5: Pull complete b169408915cf: Pull complete 8d1db2ff85db: Pull complete 2c8916db4a9b: Pull complete c816e7f9dd77: Download complete 267f6d22fa54: Download complete Digest: sha256:b7003f7d4f383dd32d210ba08f24c840276fdf92c211bd18a798d47647311290 Status: Downloaded newer image for docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2 docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2
完成后,再写下面的 Compose。
Docker Compose 部署
游戏和封面在宿主机目录 data/,对应容器内 /data。配置档案在 config/,对应容器内 /config。docker compose down 不加额外参数时,不会删除这两个目录。
容器内管理界面监听 3000,游戏页面监听 80。本文把宿主机 13300 映到容器 3000,把宿主机 8080 映到容器 80。宿主机 3000 常被本机前端开发占用;宿主机 80 常被已有网站占用。管理页上若仍写端口 3000 或 80,按本文的宿主机端口访问。
以前用 docker run 创建过同名容器时,先删掉容器再启动 Compose。这一步不删除 config/ 和 data/。
- 确认宿主机 13300 和 8080 没有被占用:
bashss -tlnp | grep -E ':8080|:13300' || true
Ubuntu 24.04 上这条命令没有输出,说明 8080 和 13300 都空着。有输出时,把后面 Compose 里对应的左侧端口改成空闲端口,访问时改用新的宿主机端口。
- 创建目录,并把所有权交给
1000:1000。容器默认以这个用户读写config/和data/。
bashsudo mkdir -p /www/wwwroot/emulatorjs/{config,data} sudo chown -R 1000:1000 /www/wwwroot/emulatorjs
id 不是 1000 时,把这里的数字和 Compose 里的 PUID、PGID 改成同一个用户。macOS 使用:
bashmkdir -p ~/docker/emulatorjs/{config,data} cd ~/docker/emulatorjs
同名容器还在时:
bashdocker rm -f emulatorjs
- 写入
compose.yaml:
bashcd /www/wwwroot/emulatorjs cat > compose.yaml <<'EOF' name: emulatorjs services: emulatorjs: image: docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2 container_name: emulatorjs restart: unless-stopped environment: PUID: "1000" PGID: "1000" TZ: "Asia/Shanghai" ports: - "13300:3000" - "8080:80" volumes: - ./config:/config - ./data:/data EOF
- 启动:
bashdocker compose up -d
- 查看状态。
STATUS为 running 后,再打开浏览器。容器马上退出时,用日志看原因,常见情况是端口已被占用,或data/没有写权限。
bashdocker compose ps docker compose logs --tail 80 emulatorjs
Ubuntu 24.04 实测,up -d 后容器保持运行,日志里能看到 UID 1000 和 IPFS 初始化:
text[+] up 2/2 ✔ Network emulatorjs_default Created ✔ Container emulatorjs Started NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS emulatorjs docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2 "/init" emulatorjs 5 seconds ago Up 3 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp, 0.0.0.0:13300->3000/tcp, [::]:13300->3000/tcp [migrations] started [migrations] no migrations found User UID: 1000 User GID: 1000 initializing IPFS node at /data/.ipfs Initializing daemon... Kubo version: 0.24.0 Repo version: 15 System version: amd64/linux Golang version: go1.21.10
浏览器里整理游戏
游戏文件涉及版权。本文实测只用 MAME 官方提供的 Circus(Exidy,1977)。权利人 H.R. Kauffman 允许把这份 ROM 用于免费、非商业用途,下载前页面要求勾选确认。其他游戏请自行确认你有权使用后再放入对应目录。本文不提供商业游戏的下载地址。
管理界面地址是 http://服务器IP:13300。顶部有 Rom Management、Config Management、File Management、Profile Management、Custom Metadata。
- 打开
http://服务器IP:13300。页面写着 Please Download the default fileset to begin 时,点「Download」。

容器要能访问外网。点下去之后会弹出下载列表,条目从 Downloading 变成 Downloaded。滚到列表顶部,出现 Downloaded All Files 就可以进行下一步。第一次没有文件落下时,等页面能刷新后再点一次「Download」。


- 打开「Rom Management」。还没放入游戏时,页面写 No roms found please add some to continue。旁边的 Default 卡片是封面素材,Available 275,Downloaded 还是 0。这张图就是还没点「DL/Update」的样子。

先点这张卡片上的「DL/Update」,把 275 项素材下完。下完后的数字出现在后面的扫描截图里,Downloaded 变成 275。
下面四页这次都不用改,对照一下即可。
「Config Management」左侧是主机名,右侧是该主机的 JSON。图中打开的是 3do,items 还是空的,右上角是「Save」。这不是 Circus 的配置。

「File Management」能看到「Download」建好的目录。以 3do 为例,下面有 backgrounds、bios、corners、roms,属主是 abc。

「Profile Management」里已经有 default。图中用户名和密码是空的,本文没有新建档案,Circus 用 default 进入。这里的账号不是管理界面的登录。

Custom Metadata 这时还是说明文字。以后若手动给游戏补了封面或名称,才会出现在左侧。

- 打开 Circus 的 MAME 官方页面,勾选非商业使用确认后再下载。把得到的压缩包保存为
circus.zip,放到街机目录:
text/www/wwwroot/emulatorjs/data/arcade/roms/circus.zip
容器内同一位置是 /data/arcade/roms/circus.zip。拷进目录之后把所有权交回 1000:1000,扫描进程才能读到:
bashsudo chown -R 1000:1000 /www/wwwroot/emulatorjs/data
「File Management」打开 /arcade/roms/,能看到 circus.zip,约 6 KB,属主 abc。

- 回到「Rom Management」,点左侧的 arcade,再点卡片上的「Scan」。扫完后这张卡片是 Roms 1、Scanned 1,Default 的 Downloaded 是 275。街机按 zip 文件名识别,
circus.zip对上的就是 Circus。

同一页往下的 Rom Identification basics 写着后续三步:从左侧打开这一项,为已识别的游戏下载封面,再把游戏写入该主机的配置。菜单写在宿主机的 data/config/arcade.json。这个文件在 data/ 里,不是上面的 config/ 档案目录。
你自己另有合法来源的游戏,按下面的目录名放进对应的 roms。红白机是 /www/wwwroot/emulatorjs/data/nes/roms/。
| 目录 | 主机 |
|---|---|
nes | 红白机 / NES |
snes | 超任 / SNES |
gb | Game Boy |
gbc | Game Boy Color |
gba | Game Boy Advance |
n64 | Nintendo 64 |
nds | Nintendo DS |
segaMS | Master System |
segaMD | Mega Drive / Genesis |
segaGG | Game Gear |
segaCD | Sega CD |
segaSaturn | Saturn |
segaSG | SG-1000 |
sega32x | Sega 32X |
psx | PlayStation |
pce | PC Engine / TurboGrafx-16 |
arcade | 街机 |
atari2600 | Atari 2600 |
atari5200 | Atari 5200 |
atari7800 | Atari 7800 |
lynx | Atari Lynx |
jaguar | Atari Jaguar |
colecovision | ColecoVision |
odyssey2 | Odyssey² |
msx | MSX |
ngp | Neo Geo Pocket |
ws | WonderSwan |
vb | Virtual Boy |
vectrex | Vectrex |
3do | 3DO |
doom | Doom |
sega32x 目录可以放文件。管理页写明,32X 目前没有可分发的可用核心。
卡带可以放 zip,也可以放解压后的 ROM。NES 用带文件头的 .nes,N64 用大端 .z64,PlayStation 优先用 CHD。对不上名录的文件会留在未识别列表里,在左侧打开该主机后手动填名称。规则的全文在管理页的 Rom Identification 里。
打开游戏并操作
游戏页面地址是 http://服务器IP:8080。配置写完后,这里是街机菜单:左侧是机台,右侧是 ARCADE。机台屏幕上的画面是菜单装饰,还不是 Circus 的游戏画面。

点进 Circus 之后才是游戏。标题画面写着 PLAYER ONE JUMPS PLAYER TWO,下面是跷跷板和小人。这是转盘操作的街机,在浏览器里用鼠标在画面上移动即可试玩。

用键盘方向键或手柄在菜单里移动。Libretro 核心的游戏内菜单可以用键盘 F1 打开;手柄上同时按 Start、Select、L、R 也能打开。触屏时用左上角的按钮。
存档和部分设置写在当前这个网址下的浏览器里,不在容器磁盘上。离开游戏时用页面上的返回,让 SRAM 有机会写回浏览器。直接关掉标签,这次进度可能还留在内存里。以后如果改了主机名或端口,浏览器会把它当成另一个网址,原来的存档不会跟着走。
Chromium 系浏览器(Chrome、Edge、Brave)是管理页推荐的选择。Apple 设备使用 Safari,并从 Safari 添加到主屏幕后再打开,以获得全屏。管理页写明 Firefox 的手柄不可用。
访问范围和可选设置
管理界面没有登录。打开 13300 的人可以上传文件、扫描目录和改菜单,8080 会把 data/ 里的游戏交给浏览器。两处只在局域网使用。放到云服务器上时,安全组只放行你自己的地址。
封面收集完成后,如果不再下载素材,可以停掉容器内的 IPFS。在 compose.yaml 的 environment 里增加 DISABLE_IPFS: "true",然后执行 docker compose up -d。第一次 Download 还没完成时不要加这项,默认封面依赖 IPFS。
端口 4001 是 IPFS 互连端口。本文默认不发布它。只有你打算把封面继续分发给别人时,才考虑把它映出来,并只对需要的来源放行。
已经有一整套游戏目录、只想只读挂进容器时,在 compose.yaml 的 volumes: 里、./data:/data 下面加一行。红白机示例:
yaml- /path/to/nes/roms:/roms/nes:ro
/path/to/nes/roms 里直接放游戏文件。容器启动时会把这个目录链接到 /data/nes/roms。改完后执行 docker compose up -d。data/nes/roms 里已经有别的文件、并且还不是链接时,先移走那些文件再启动,链接才会建上。
反向代理要挂在子路径下时,设置 SUBFOLDER,值为带首尾斜杠的路径,例如 /emu/。默认不需要这项。
Nintendo DS 和 Saturn 的线程版核心需要 HTTPS。管理页写明,要把站点放到带证书的反向代理后面,再把配置里的模拟器改成 libretro-melonds_threaded 或 libretro-yabause_threaded。普通 HTTP 使用默认核心即可。
容器内的静态页面在 /emulatorjs/frontend,其中 user 指向 /data。整理完成后,官方说明允许把这组文件拷到别的网站上托管;拷贝时要连同游戏和封面一起考虑,因为页面通过这个链接读取 /data。
备选:docker run
没有 Compose 时用这一节。目录、端口和镜像与上面相同。Compose 已经在跑时,先到工作目录执行 docker compose down,再跑下面的命令,避免容器名和端口冲突。
bashdocker rm -f emulatorjs docker run -d --name emulatorjs --restart unless-stopped -e PUID=1000 -e PGID=1000 -e TZ=Asia/Shanghai -p 13300:3000 -p 8080:80 -v /www/wwwroot/emulatorjs/config:/config -v /www/wwwroot/emulatorjs/data:/data docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2
macOS 把两处 /www/wwwroot/emulatorjs 换成 ~/docker/emulatorjs。
查看状态:
bashdocker ps --filter name=emulatorjs docker logs --tail 80 emulatorjs
然后打开 http://服务器IP:13300 下载默认文件,再打开 http://服务器IP:8080 游玩。
停掉并删除容器,保留游戏文件:
bashdocker stop emulatorjs && docker rm emulatorjs
常见问题
Q1:latest 拉不下来?
LinuxServer 已于 2025 年 6 月 11 日弃用这个镜像。停止维护说明写明,弃用之后不能再拉取 latest 和 develop、nightly 这类分支标签。本文命令使用 1.9.2。
Q2:这和 emulatorjs.com 是同一个项目吗?
linuxserver/emulatorjs 是 LinuxServer 的自托管前端,模拟核心主要来自 Libretro。emulatorjs.com 上的 EmulatorJS 是另一套仍在开发的浏览器模拟器。两边的镜像、端口和配置各自独立,不能拿这份 Compose 去启动另一套。
Q3:为什么访问地址是 13300 和 8080?
容器内仍然是 3000 和 80。宿主机改成 13300 和 8080,是为了避开本机前端常用的 3000,以及已经占用 80 的网站。管理页上的说明仍按容器端口来写,打开时换成本文的宿主机端口。
Q4:管理界面打不开?
先看 docker compose ps 是否为 running。马上退出时看 docker compose logs --tail 80 emulatorjs。端口已被占用时,改 Compose 左侧端口后执行 docker compose up -d。日志停在 IPFS 初始化时,再看 data/ 的属主是不是 PUID 里的用户;不是的话交给 1000:1000 后重新启动。
Q5:Download 之后 data/ 仍是空的?
默认素材走容器内的 IPFS,容器要能访问外网。管理页写明可以多点几次「Download」,已有的文件会走本地 IPFS,缺少的会重试。DISABLE_IPFS 已经设上时,先删掉这项并重新启动,再下载。
Q6:扫描不到游戏?
文件放在 /data/<目录名>/roms/,目录名在前,roms 在后。本文的 Circus 在 /data/arcade/roms/circus.zip。拷贝之后执行 chown -R 1000:1000,再在「Rom Management」里点该主机的「Scan」。
Q7:别的游戏从哪里下载?
本文只给出 MAME 官方、权利人允许非商业使用的 Circus。其他游戏请自行寻找你有权使用的文件,再放到正文目录表里对应的 roms。
Q8:很多游戏显示未识别?
识别依赖文件校验和,对照的是 No-Intro、Redump 一类名录。改版、汉化或文件头不同的 ROM 对不上。在左侧打开该主机,手动关联到正确名称,再下载封面并写入配置。
Q9:存档找不到了?
存档写在浏览器里,并且跟当时的完整网址绑定。换了 IP、端口或浏览器,就会变成另一份存档。离开游戏时用返回,不要直接关标签。
Q10:手机上光盘游戏进不去?
管理页写明,完整光盘游戏需要较大内存,手机和主机浏览器通常带不动。非光盘主机更适合这些设备。N64 同样吃内存。
Q11:如何升级?
这个镜像已经停止更新,没有比 1.9.2 更新的版本。保持 Compose 里的 1.9.2。不要改成 latest。
Q12:还有哪些仍在维护的选择?
LinuxServer 在停止维护说明里给出了三个方向:RomM、Gaseous Server、webRcade。它们的目录结构和端口与本文不同,不能沿用这份 Compose。
Q13:如何停止?
还要保留游戏和封面时,不要删除 config/ 和 data/。
bashcd /www/wwwroot/emulatorjs docker compose down
这套收藏不再需要时,再删除工作目录:
bashrm -rf /www/wwwroot/emulatorjs
命令速查
bashdocker pull docker.xuanyuan.run/linuxserver/emulatorjs:1.9.2 cd /www/wwwroot/emulatorjs docker compose up -d docker compose ps docker compose logs --tail 80 emulatorjs docker compose down
没有 Compose 时用「备选:docker run」里的命令。管理界面是 http://服务器IP:13300,游戏页面是 http://服务器IP:8080。本文实测的 circus.zip 在 data/arcade/roms/。
延伸阅读
阅读原文
本文基于 linuxserver/emulatorjs:1.9.2,以 1.9.2 版本实测,容器内 IPFS Kubo 0.24.0,测试平台 Ubuntu 24.04 Linux。
评论交流
免责声明
本博客文章所提供的内容、技术方案、配置示例及部署指南等信息,仅供学习交流和技术参考使用。文章内容基于发布时的技术环境和版本信息编写,可能因时间推移、技术更新或环境差异而存在不适用的情况。
用户在参考本博客内容进行部署操作前,应当充分了解相关技术风险,并建议在测试环境中进行充分验证和测试,确认无误后再考虑在生产环境中使用。生产环境部署前,请务必进行数据备份,并制定相应的回滚方案。
用户因使用本博客内容进行部署操作而产生的任何损失、数据丢失、系统故障、安全风险或其他问题,均由用户自行承担全部责任。轩辕镜像官方不对因使用本博客内容而产生的任何直接或间接损失承担责任。
本免责声明的最终解释权归轩辕镜像官方所有。
