DNS is now your own party server. tooling/dns-records.sh prints the records (online = service names; airgap = + Mojang/launcher/NeoForge spoofs) in several formats. build-stack --up online|airgap selects the mirror layer and prints the matching records. dnsmasq moved to an optional override (docker-compose.dnsmasq.yml). Default domain switched to .lan (.local is hijacked by mDNS on unicast DNS). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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)
- Open
http://ulicraft.local, download FjordLauncherUnlocked for your OS. - Add an authlib-injector account, URL
http://auth.ulicraft.local/authlib-injector(register/login athttp://auth.ulicraft.local). - (Air-gap) Trust the Caddy root CA; in Settings → APIs set Assets Server to the mirror if not using DNS spoofing.
- Import the pack:
http://packwiz.ulicraft.local/pack.toml. - 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 (00–12)
See plan/00-overview.md for the full design.