DEV Community

Cover image for Stop sending mods via Discord: self-hosted MiniMin panel + one-click sync for friends
quonaro
quonaro

Posted on

Stop sending mods via Discord: self-hosted MiniMin panel + one-click sync for friends

MiniMin is a small self-hosted panel for modded Minecraft servers. Spin up a server in Docker in a couple of clicks, hit "share client build", your friend pastes one link into MiniMin Sync and gets a ready Prism Launcher instance with auto-updates. No Discord zips, no version drift.

If this resonates — clone the repos, run it, tell me what breaks first. That's the most useful feedback right now.

Dashboard Info

The pain

Every 6-12 months I get the same urge: play Minecraft with friends, with a couple hundred Fabric / Forge mods.

The usual flow:

  1. Someone spins up a server.
  2. Mods are copied by hand into mods.
  3. The client build is zipped and dropped into Discord.
  4. One friend has the wrong version, another one crashes, the third one is too lazy to update.

I wanted this instead:

  1. Open a panel in the browser.
  2. Create / update the server.
  3. Hit "create client archive".
  4. Send friends one link. They sync in one click.

That's why I built MiniMin. Mostly for myself, no enterprise / clean-code claims. Part of the code is AI-assisted (Devin + Kimi K2.6), I was acting as a user annoyed by manual mod setup.

Why not plain Docker or Pterodactyl?

1. Bare server on the host
Works, but: Java versions, logs, crash reports, manual client build. OK for once, painful for regular weekend sessions.

2. Docker / docker compose + itzg/minecraft-server
Better. The itzg/minecraft-server image solves 80% of Java / version / EULA / memory pain. But the routine stays: drop in mods, read logs, build a client archive, send it around.

I started with scripts. Then I wanted a panel: click a few buttons and be sure friends run the same build.

3. Pterodactyl
Powerful, but huge. It's hosting infrastructure for dozens of servers with wings, users, allocations. Overkill for "1-2 modpacks for friends for the weekend".

So my own bike: a tiny panel only for modded servers + a tiny desktop sync client.

How it works: two parts

1. MiniMin — server panel

Single container: Go backend + embedded frontend, Caddy in front.

What it does today (verified against code, no marketing):

  • Multi-server — several servers from one dashboard: start / stop / restart / monitoring.
  • Docker orchestration — one container per server via Docker API, based on itzg/minecraft-server. Host /var/run/docker.sock, volumes mapped to the host. Not OK for SaaS, fine for home self-hosting.
  • First-class engines are VANILLA / FABRIC / FORGE only. NeoForge is aliased to FORGE, Quilt to FABRIC. Versions come only from Mojang + Paper + meta.fabricmc.net + Forge promotions_slim.json. No separate NeoForge/Quilt API.
  • Files in the browser — browse server dirs, upload, download, edit configs.
  • Live logs + RCON console over WebSocket.
  • Players — online, ban, op, whitelist.
  • Mods — install / remove / enable / disable server and client mods (mods-client/*.jar, resourcepacks, shaderpacks, including *.disabled). Search and metadata only via Modrinth (modrinth is the only ContentSource). No CurseForge API integration.
  • Crash reports and resources — separate tabs, no need to dig files.
  • Client archive in 4 formats — zip, mrpack, curseforge, prism:
    • zip: plain mods/, resourcepacks/, shaderpacks/.
    • mrpack: overrides/mods/... + modrinth.index.json, but simplified — FormatVersion: 1, VersionID: 1.0.0, empty Hashes: {}, no dependencies.
    • curseforge: manifest.json with minecraftModpack + overrides/.... Export only, not CurseForge API.
    • prism (main one for Sync): instance.cfg + mmc-pack.json + .minimin.json (serverId, token, baseUrl) + files under .minecraft/mods/.... Plus /info, /manifest (with SHA-256), /file/{path} endpoints for diff sync.
  • Scheduled backups already exist — via scheduler (interval|cron|event -> backup with keepLast/keepDays). But only world/ is archived to tar.gz + save-off/save-all via RCON.
  • No multi-user — single static ORCHESTRATOR_API_KEY (Bearer/cookie/query). No OpenWrt build.

Frontend: Nuxt 4 + Vue 3 + TailwindCSS. Backend: Go + Docker SDK, REST + WebSocket.

2. MiniMin Sync — desktop client for friends

A friend doesn't need a panel. Paste a link, hit sync, play.

Sync is a separate Wails app (Go + Vue 3 + Tailwind, no Electron). No button inside Prism: it launches instances externally via CLI --launch.

How it works (as in code):

  • Works only with ?format=prism. Downloads the prism-zip, extracts with Zip Slip protection, writes a .minimin.json marker (serverId, token, baseUrl, lastSyncAt).
  • Update check — diffs local files against /manifest: missing / outdated (size or SHA-256 mismatch) / orphan (extras only in top level of mods, resourcepacks, shaderpacks, scripts, kubejs, defaultconfigs).
  • Update — downloads only changed files with 4 workers, renames old ones to .minimin-backup/, checks free disk space, post-verifies SHA-256, rolls back on mismatch.
  • Background check — yes, but only while the app is open: autoCheckLoop every 5 min by default. No OS autostart, no tray.
  • Launchers — only 3 hardcoded: Prism Launcher, ElyPrismLauncher, MultiMC (standard paths + PATH, --launch <serverID> with fallbacks). Nothing else.
  • Knows nothing about game / loader versions — just mirrors files from the server.

Simple idea: server side owns "how to build", client side owns "how not to break the player".

Run in 5 minutes

Only Docker and Docker Compose needed.

mkdir minimin && cd minimin

# fetch compose and env template
curl -O https://raw.githubusercontent.com/quonaro/minimin/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/quonaro/minimin/main/.env.example

# edit .env — ORCHESTRATOR_API_KEY and MC_SERVERS_HOST_DIR are required
nano .env

docker compose up -d

# open http://localhost:8081
Enter fullscreen mode Exit fullscreen mode

All in one .env:

  • ORCHESTRATOR_API_KEY — login key
  • MC_SERVERS_HOST_DIR — host path for servers
  • ORCHESTRATOR_LOG_LEVEL, MC_SERVERS_DIR, MODRINTH_CUSTOM_URL — optional

For dev there is dev.yml with hot reload: frontend on :3000, backend on :8081.

Feedback needed

This covers my flow: gather for a weekend, play, split.

  1. mrpack is simplified now (no hashes / deps). Do you need a full Modrinth format with hashes, or is prism + zip enough? Do you need import of foreign mrpack / CurseForge packs?
  2. NeoForge is now an alias to FORGE, Quilt to FABRIC, no separate versions. Do you need honest NeoForge/Quilt with separate loader versions, or is aliasing enough?
  3. Sync is a separate window now, check every 5 min only while open, no autostart/tray, launchers only Prism/ElyPrism/MultiMC. What matters more: autostart + tray, headless daemon without GUI, or more launchers?
  4. Scheduled backups exist but only world/ + keepLast/keepDays. Do you need full server backup (mods+configs), or is world enough? Is multi-user needed (single API key now)? Are docs for weak hardware / OpenWrt interesting?

Thanks for reading. I'll catch all the tomatoes.

Screenshots (Server):

Logs

Logs

Server properties:

Properties

File Manager

File Manager

Crash Report

Crash Report

RCON Console

RCON Console

Players

Players

Client / Server Resources (Mods)

Mods

Modrinth Library Integration

Modrinth

Export Page

Export Page

Screenshots (Client):

Sync#1

Sync#2

Sync#3

Sync#4

Top comments (1)

Collapse
 
launchgatecheck profile image
Launch Gate •

I'd prioritize a consistent build snapshot before tray support. A useful fixture: fetch the manifest for build A, publish build B while the four downloads are running, then interrupt one download. Do the file endpoints pin A, and does rollback restore the complete old instance, including any orphan files removed during sync? Per-file hashes can catch a mismatch without proving the whole instance belongs to one build. Showing a build ID in the manifest and completion screen would help friends tell "fully synced" from "some files updated; old build restored."