tooling/issue-letsencrypt.sh: manual script, issues one cert per name (apex + LE_SUBDOMAINS) with acme.sh + OVH DNS-01 (no inbound ports), installs to certs/<name>/ and reloads nginx. nginx/ulicraft.conf.tmpl: TLS vhosts for apex/pack (static) + auth (proxy to drasl). OVH creds and LE_EMAIL in .env.example; certs/ gitignored. plan/15-letsencrypt.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ulicraft Server
Self-hosted modded Minecraft (NeoForge 1.21.1) for a LAN party. Runs online (internet present) or fully air-gapped (offline). Self-hosted auth/skins (Drasl), a packwiz modpack, a guest landing page, and a transparent mirror of Mojang/launcher/NeoForge 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, auth/packwiz proxy, asset mirror (tls internal) |
mc-backup |
itzg/mc-backup | world backups every 6h via RCON |
Optional layers: avahi (mDNS .local), dnsmasq (turnkey DNS) — only if you
don't run your own DNS.
Config lives in .env: BASE_DOMAIN (default ulicraft.lan), HOST_LAN_IP,
RCON_PASSWORD, ENABLE_MDNS. Services are subdomains: auth., packwiz.,
mc., plus the apex landing page.
DNS (pick one)
Names must resolve to HOST_LAN_IP. Use your own party DNS server (recommended):
tooling/dns-records.sh online # service names only (internet present)
tooling/dns-records.sh airgap # + Mojang/launcher/NeoForge spoofs (offline)
Paste the printed records into your DNS. Use .lan, not .local — .local
is intercepted by mDNS and won't resolve via unicast DNS.
Alternatives: ENABLE_MDNS=true + BASE_DOMAIN=*.local (zero-config mDNS,
macOS/Linux native, Windows needs Bonjour) — but mDNS can't serve the air-gap
upstream spoofs. Or layer in docker-compose.dnsmasq.yml for a turnkey DNS.
Simulate the DNS via /etc/hosts (single machine, testing)
No DNS server? Append the records straight to /etc/hosts (marker-wrapped):
tooling/dns-records.sh online hosts | sudo tee -a /etc/hosts # services only
tooling/dns-records.sh airgap hosts | sudo tee -a /etc/hosts # + Mojang spoofs
Remove them later:
sudo sed -i '/# >>> ulicraft/,/# <<< ulicraft/d' /etc/hosts
Notes:
/etc/hostshas no wildcard — only the listed names resolve.airgapadds the Mojang/launcher/NeoForge hostnames pointing at the LAN; this breaks normal internet access to those domains while present. Use it only while offline; for an online box useonline.mc.<domain>works (client defaults to :25565); SRV isn't used via/etc/hosts.
Prerequisites
Docker + Docker Compose; for prep also pnpm, packwiz, jq, curl,
envsubst, sha1sum.
Launch
cp .env.example .env # set BASE_DOMAIN, HOST_LAN_IP, RCON_PASSWORD
tooling/build-stack.sh --prep # ONLINE, once: render, build landing,
# mirror mods+assets, fetch launcher +
# authlib, pre-bake the server volume
tooling/build-stack.sh --up online # internet present, no mirror
tooling/build-stack.sh --up airgap # offline; full asset mirror on :443 (default)
tooling/build-stack.sh --up core # base only (debugging)
--up prints the DNS records to add for that mode. Down:
docker compose -f docker-compose.yml -f docker-compose.static.yml -f docker-compose.mirror.yml down
Join (guests)
- Open
http://ulicraft.lan, download FjordLauncherUnlocked for your OS. - Add an authlib-injector account, URL
http://auth.ulicraft.lan/authlib-injector(register/login athttp://auth.ulicraft.lan). - Air-gap only: trust the local CA — download
http://ulicraft.lan/ca.crtand add it to your OS trust store (so the HTTPS asset mirror is trusted). - Import the pack:
http://packwiz.ulicraft.lan/pack.toml. - Connect to
mc.ulicraft.lan(no port needed).
Layout
docker-compose.yml # core: drasl, minecraft, mc-backup, caddy
.static.yml .mirror.yml # online/airgap layers
.avahi.yml .dnsmasq.yml # optional DNS layers
caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets (core/static/mirror)
drasl/config/config.toml.tmpl # auth config (rendered)
dnsmasq/dnsmasq.conf.tmpl # optional DNS config (rendered)
docker/avahi/ # mDNS responder image
pack/ # packwiz modpack source
landing/ # Astro site → www/
tooling/ # build-stack, render-config, dns-records,
# mirror-mods, mirror-airgap, fetch-launcher
mirror/ # vendored jars + launcher + asset mirror (gitignored)
plan/ # full design docs (00–12)
See plan/00-overview.md for the full design.