Oier Bravo Urtasun 8eeda1265c feat(mdns): optional Avahi .local responder (ENABLE_MDNS)
Self-contained host-network Avahi container publishes the service names over
mDNS so .local works zero-config for guests (macOS/Linux native, Windows needs
Bonjour). Toggle with ENABLE_MDNS=true + BASE_DOMAIN=*.local; build-stack layers
docker-compose.avahi.yml in and builds the image during --prep. mDNS can't serve
the air-gap upstream spoofs (non-.local) — those still need real DNS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 05:56:54 +02:00
2026-05-24 04:03:42 +02:00
2026-05-24 04:03:42 +02:00

Ulicraft Server

Self-hosted modded Minecraft (NeoForge 1.21.1) for a LAN party, designed to run fully offline. Self-hosted auth/skins (Drasl), a packwiz modpack, a guest landing page, and a transparent mirror of Mojang/launcher assets so guest laptops need no internet.

Stack

Service Image Role
minecraft itzg/minecraft-server NeoForge 1.21.1 server, :25565
drasl unmojang/drasl Yggdrasil auth + skins (password login)
caddy caddy:alpine ingress: landing page, auth/packwiz proxy, asset mirror
dnsmasq jpillora/dnsmasq LAN DNS *.ulicraft.local + Minecraft SRV
mc-backup itzg/mc-backup world backups every 6h via RCON

Everything keys off two vars in .env: BASE_DOMAIN (default ulicraft.local) and HOST_LAN_IP. Services are subdomains: auth., packwiz., plus the apex landing page.

Prerequisites

Docker + Docker Compose, and for prep: pnpm, packwiz, jq, curl, envsubst, sha1sum.

Setup

cp .env.example .env          # set BASE_DOMAIN, HOST_LAN_IP, RCON_PASSWORD

Point guest laptops' DNS at HOST_LAN_IP (resolves *.ulicraft.local). On the Docker host, free port 53 if systemd-resolved holds it (dnsmasq binds the LAN IP only).

Launch

tooling/build-stack.sh --prep            # ONLINE, run once: render configs, build
                                         # landing, mirror mods+assets, fetch
                                         # launcher, pre-bake the server volume

tooling/build-stack.sh --up [mode]       # bring the stack up
#   core    base only (no static site, no mirror)
#   static  + apex landing page + launcher downloads
#   mirror  + full air-gap asset mirror (HTTPS :443)
#   full    static + mirror (default)

Down: docker compose -f docker-compose.yml -f docker-compose.static.yml -f docker-compose.mirror.yml down

Join (guests)

  1. Open http://ulicraft.local, download FjordLauncherUnlocked for your OS.
  2. Add an authlib-injector account, URL http://auth.ulicraft.local/authlib-injector (register/login at http://auth.ulicraft.local).
  3. (Air-gap) Trust the Caddy root CA; in Settings → APIs set Assets Server to the mirror if not using DNS spoofing.
  4. Import the pack: http://packwiz.ulicraft.local/pack.toml.
  5. Connect to mc.ulicraft.local (no port — SRV record).

Layout

docker-compose.yml + .static.yml + .mirror.yml   # core + optional layers
caddy/Caddyfile + conf.d/*.caddy                 # ingress vhost snippets
drasl/config/config.toml.tmpl                    # auth config (rendered)
dnsmasq/dnsmasq.conf.tmpl                         # LAN DNS + SRV (rendered)
pack/                                            # packwiz modpack source
landing/                                          # Astro site → www/
tooling/                                          # build-stack + render/mirror/fetch scripts
mirror/                                           # vendored jars + launcher + asset mirror (gitignored)
plan/                                             # full design docs (0012)

See plan/00-overview.md for the full design.

Description
No description provided
Readme 79 MiB
Languages
Astro 24.6%
CSS 21.7%
JavaScript 16.1%
TypeScript 15%
Shell 11.3%
Other 11.3%