13 KiB
Minecraft LAN Party Server — Project Context
Self-hosted modded Minecraft server for a LAN party (~8 players), with persistent auth/skin infrastructure designed to outlive the LAN weekend and integrate with the existing homelab.
Goals & Constraints
- Players: 4–10 friends, LAN-based
- Modpack vibe: Kitchen-sink (tech + magic + exploration + QoL)
- Pack approach: Manually curated (~50–100 mods), NOT a forked existing pack
- Minecraft version: 1.21.1
- Mod loader: NeoForge (NOT classic Forge — see Decisions below)
- Network: LAN party context, but stack is persistent (lives past the weekend)
- Auth: Self-hosted Yggdrasil-compatible (Drasl) + Keycloak OIDC
- Skins: Handled by Drasl
- Persistence horizon: Long-term homelab service, not disposable
Existing Homelab Context (relevant to this project)
- Keycloak 26.0.7 running in Docker Compose (PostgreSQL 17, production mode, bind-mounted secrets, xforwarded proxy headers). This is the IdP for everything.
- NetBird as the proxy/networking layer for remote access (NOT used for the LAN party itself — see Decisions).
- AdGuard Home as recursive DNS resolver — used here for
*.home.localLAN records. - Multi-VPS topology: this Minecraft project likely runs on a local box or beefier homelab node, not a VPS (RAM/CPU requirements).
- Operator timezone: Europe/Madrid.
Key Decisions (with reasoning)
NeoForge over classic Forge
Despite the user originally saying "Forge", the 1.21.x kitchen-sink ecosystem
(ATM10, Leaking Kitchen Sink, etc.) is overwhelmingly NeoForge in 2025/2026.
The original Forge team essentially migrated to NeoForge. itzg/minecraft-server
supports both via TYPE=NEOFORGE. Going with NeoForge 1.21.1.
Drasl over Ely.by or vanilla offline mode
- Ely.by: not self-hosted, Russian-hosted, no control over identity layer.
- Vanilla offline + no auth server: works for one weekend but skins break, no persistent identity, doesn't match homelab philosophy.
- Drasl: self-hosted Go service, Yggdrasil-compatible, drop-in Mojang replacement, supports skins + capes, has first-class OIDC support with PKCE (perfect for Keycloak), actively maintained (current version 3.4.2+). GPLv3 licensed.
LAN (not NetBird) for the party itself
NetBird is the homelab's remote access layer, but adding a mesh VPN on top
of a LAN party introduces complexity for guests with no upside. Drasl and
Minecraft live on the LAN; clients reach them via drasl.home.local resolved
through AdGuard.
Manual curation (user choice — flagged as expensive)
User insisted on manual curation despite my pushback that forking ATM10 or Leaking Kitchen Sink would save weeks of crash-log triage. Note for future sessions: if curation gets painful, the fork-and-trim option is still on the table — Leaking Kitchen Sink is the closest match to the requested vibe.
itzg/minecraft-server as base image
The de facto standard. Handles NeoForge installation automatically via
TYPE=NEOFORGE + VERSION + NEOFORGE_VERSION. Supports Modrinth and
CurseForge mod auto-download via MODRINTH_PROJECTS and CURSEFORGE_FILES.
Architecture
LAN segment
│
├── AdGuard Home → resolves drasl.home.local, keycloak.home.local
│ to the Docker host's LAN IP
│
├── Keycloak (existing, separate compose)
│ realm: homelab
│ client: drasl (confidential, PKCE S256)
│
└── Minecraft host (this project's compose)
│
├── drasl :25585 (Yggdrasil API + web UI)
│ └── OIDC → Keycloak
│
├── minecraft :25565 (server)
│ :24454/udp (Simple Voice Chat, optional)
│ └── -javaagent:authlib-injector.jar=http://drasl.home.local:25585/authlib-injector
│ └── ONLINE_MODE=false (required for authlib-injector)
│
└── mc-backup (itzg/mc-backup, 6h interval)
└── RCON → minecraft
Clients (LAN party guests):
Prism Launcher → authlib-injector account pointing at Drasl
→ joins minecraft:25565
Auth Flow (end-to-end)
- Guest opens
http://drasl.home.local:25585in browser. - Clicks "Register with Keycloak" → redirected to Keycloak login.
- Logs in with homelab Keycloak credentials.
- Returned to Drasl, picks a player name, uploads a skin.
- Drasl shows them a "Minecraft Token" on their profile page.
- In Prism Launcher: Settings → Accounts → Add authlib-injector account
with URL
http://drasl.home.local:25585/authlib-injectorand the Minecraft Token as password. - Joins Minecraft server. Server validates session against Drasl (via authlib-injector JVM agent), Drasl confirms, player enters with their skin.
Critical Gotchas
Minecraft 1.21+ secure profile incompatibility
- Server: must set
enforce-secure-profile=false(compose:ENFORCE_SECURE_PROFILE=FALSE) - Drasl: must set
SignPublicKeys = false - These are linked. The Drasl docs explicitly warn: "Mixed authentication does
not work with
SignPublicKeys = trueon Minecraft 1.21+."
authlib-injector JVM agent
- Syntax:
-javaagent:/path/to/authlib-injector.jar=<yggdrasil-api-url> - The URL after
=is the API root, which for Drasl is<BaseURL>/authlib-injector. - Must match between server and client. If server uses
http://drasl.home.local:25585/authlib-injectorand client uses anything else (e.g. an IP), session validation fails silently with "Invalid session". - Download from: https://github.com/yushijinhun/authlib-injector/releases
- Mount into the itzg container at
/extras/authlib-injector.jar.
Keycloak reachability on LAN day
If Keycloak is only reachable via NetBird or only from the public internet, Drasl OIDC registration breaks on LAN-only day. Options:
- Expose Keycloak on the LAN (simplest)
- Pre-register all guests before the party
- Temporarily disable OIDC and allow password registration during the LAN
(
AllowPasswordLogin = true, comment out the OIDC block)
HTTP vs HTTPS for OIDC
Modern browsers warn on plain HTTP for OIDC flows but they work. For a production-grade setup, front Drasl with Caddy or Traefik using a local CA cert (smallstep/step-ca pairs well with the existing homelab). For LAN-only HTTP is fine.
NeoForge version pinning
Mods are picky about exact NeoForge minor versions. The compose currently
pins NEOFORGE_VERSION: "21.1.209" as a placeholder. Verify against
https://projects.neoforged.net/neoforged/neoforge before deploying. Don't
use "latest" for a stable server.
Drasl username vs player name
- Drasl username = OIDC user's email (used for web UI login)
- Player name =
preferred_usernamefrom OIDC, or user-chosen ifAllowChoosingPlayerName = true - Keycloak users must have email set. Verify the realm's email-as-username
setting and the
preferred_usernamemapper are configured.
Mod source preference
Prefer Modrinth over CurseForge for itzg auto-download. CurseForge
requires an API key (CF_API_KEY) and has been historically flakier with
the itzg image. Use CF only for mods that are exclusive to it.
Compose Stack
The working compose file is at ./docker-compose.yml. Key environment vars
documented inline. Adjacent files:
.
├── docker-compose.yml
├── .env # RCON_PASSWORD, CF_API_KEY (if needed)
├── drasl/
│ └── config/
│ ├── config.toml # see ./drasl-config.toml example
│ └── keycloak-client-secret # single line, no trailing newline
├── extras/
│ ├── authlib-injector.jar # downloaded from yushijinhun's releases
│ ├── modrinth-mods.txt # one mod slug per line
│ └── cf-mods.txt # only if using CurseForge mods
├── backups/ # mc-backup writes here
└── CLAUDE.md # this file
Memory sizing
With ~50–100 mods and 8 concurrent players on 1.21.1:
INIT_MEMORY: 4G,MAX_MEMORY: 10Gis a safe starting point- Use
USE_AIKAR_FLAGS: TRUEfor the well-known GC tuning - Monitor with
mc-monitor(itzg makes one) if you want metrics
Backups
itzg/mc-backup runs alongside, talks to the server via RCON, takes
snapshots every 6h, prunes after 14 days. Backups are world-only (no mod
jars), which is correct — mods are reproducible from the mod list files.
Keycloak Client Configuration
In the homelab realm (or whatever the realm is called):
-
Clients → Create client
- Client type:
OpenID Connect - Client ID:
drasl - Name:
Drasl Minecraft Auth
- Client type:
-
Capability config
- Client authentication: ON (confidential client)
- Authentication flow: Standard flow only
- Disable: Direct access grants, Implicit, Service accounts
-
Login settings
- Root URL:
http://drasl.home.local:25585 - Valid redirect URIs:
http://drasl.home.local:25585/web/oidc-callback/Keycloak(theKeycloakat the end MUST match theName = "Keycloak"in Drasl's[[RegistrationOIDC]]block — case sensitive) - Web origins:
http://drasl.home.local:25585
- Root URL:
-
Credentials tab → copy Client Secret to
./drasl/config/keycloak-client-secret(no trailing newline). -
Advanced tab → PKCE Code Challenge Method:
S256
Client Distribution (LAN guests)
Recommended launcher
Prism Launcher — open source, cross-platform (Win/Mac/Linux), first-class authlib-injector support, can import/export instance ZIPs.
Per-guest one-time setup
- Install Prism Launcher.
- Add authlib-injector account:
- URL:
http://drasl.home.local:25585/authlib-injector - Username/password: their Drasl credentials (or Minecraft Token if registered via Keycloak OIDC)
- URL:
- Import the modpack instance ZIP.
What to ship guests
- A Prism instance ZIP (right-click instance → Export Instance)
- A one-page README with the auth URL and import steps
- Optionally
authlib-injector.jarfor guests who refuse Prism
Open / Pending Work
These were on the roadmap when we ended the brainstorm:
- Mod shortlist for the kitchen-sink pack. Categories to fill:
- Performance (Embeddium/Sodium-equivalent for NeoForge, FerriteCore, ModernFix)
- Tech/automation (Mekanism, Create, Immersive Engineering, AE2)
- Magic (Ars Nouveau, Botania, Iron's Spells 'n Spellbooks)
- Storage (Sophisticated Storage/Backpacks, Functional Storage)
- Exploration (YUNG's structures, Repurposed Structures, biome packs)
- QoL (JEI, Jade, JEI Resources, Inventory Profiles Next, AppleSkin)
- World gen (Tectonic, Terralith — check NeoForge 1.21.1 compat)
- Compat glue (Polymorph for recipe conflicts)
- Map (XaeroMinimap + Xaero World Map)
- Voice (Simple Voice Chat — port 24454/udp already in compose)
- Decide on dimension/server-side performance mods (C2ME, Lithium-equivalent)
- Reverse proxy + local TLS for Drasl (Caddy + step-ca recommended, given the existing homelab)
- DNS record in AdGuard for
drasl.home.local→ Docker host LAN IP - Keycloak client creation per the section above
- Verify NeoForge version against current stable before first deploy
- Bootstrap admin in Drasl: leave
AllowPasswordLogin = trueinitially, create the admin account, then optionally disable password login to force OIDC
Reference URLs
- Drasl repo: https://github.com/unmojang/drasl
- Drasl configuration docs: https://github.com/unmojang/drasl/blob/master/doc/configuration.md
- Drasl recipes (example configs): https://github.com/unmojang/drasl/blob/master/doc/recipes.md
- authlib-injector: https://github.com/yushijinhun/authlib-injector
- itzg/minecraft-server: https://github.com/itzg/docker-minecraft-server
- itzg NeoForge docs: https://github.com/itzg/docker-minecraft-server/blob/master/docs/types-and-platforms/server-types/forge.md
- itzg/mc-backup: https://github.com/itzg/docker-mc-backup
- NeoForge versions: https://projects.neoforged.net/neoforged/neoforge
- Prism Launcher: https://prismlauncher.org/
- Reference modpacks for inspiration (NOT forking):
- ATM10 (Modrinth/CurseForge — ~500 mods, NeoForge 1.21.1)
- Leaking Kitchen Sink (CurseForge — trimmed ~150 mods, NeoForge 1.21.1)
Communication Preferences (carry-over from past sessions)
- Concise and direct
- Diagnose and resolve without demanding detailed reproduction steps
- Prefer iterative single-change updates over large rewrites
- Cautious scripts that halt on ambiguity rather than proceeding silently
- Avoid over-engineered responses when a simple answer suffices
- Spanish or English fine; user is comfortable in both