# ────────────────────────────────────────────────────────────────── # Ulicraft LAN-party stack — Minecraft 1.21.1 NeoForge + Drasl auth # ────────────────────────────────────────────────────────────────── # Topology (single source of config: BASE_DOMAIN + HOST_LAN_IP in .env): # - dnsmasq resolves *.${BASE_DOMAIN} -> HOST_LAN_IP for guest laptops # - caddy is the only HTTP ingress; holds mcnet aliases so the stack # resolves auth.${BASE_DOMAIN} / packwiz.${BASE_DOMAIN} internally too # - drasl handles auth (password login; NO Keycloak/OIDC in this stack) # - packwiz pack is served by caddy at packwiz.${BASE_DOMAIN} # - minecraft installs mods via PACKWIZ_URL, authlib-injector -> auth. # ────────────────────────────────────────────────────────────────── services: dnsmasq: image: jpillora/dnsmasq container_name: dnsmasq restart: unless-stopped ports: # Bind DNS to the host LAN IP so it doesn't clash with systemd-resolved on :53. - "${HOST_LAN_IP}:53:53/udp" - "${HOST_LAN_IP}:53:53/tcp" volumes: - ./dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf:ro networks: - mcnet # The webproc UI (:8080) can be exposed later behind Caddy as dns.${BASE_DOMAIN}; # guard it with HTTP_USER / HTTP_PASS env vars. # environment: # HTTP_USER: "admin" # HTTP_PASS: "change-me" caddy: image: caddy:alpine container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" # tls internal — air-gap mirror vhosts (see 11) environment: BASE_DOMAIN: ${BASE_DOMAIN} volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro - ./www:/srv/www:ro - ./pack:/srv/pack:ro - ./mirror/launcher:/srv/www/launcher:ro - ./mirror/upstream:/srv/mirror:ro # byte-exact upstream mirror (see 11) networks: mcnet: aliases: - "auth.${BASE_DOMAIN}" - "packwiz.${BASE_DOMAIN}" # Air-gap spoof hosts -> Caddy, so internal containers resolve them too. - "meta.prismlauncher.org" - "piston-meta.mojang.com" - "piston-data.mojang.com" - "libraries.minecraft.net" - "maven.neoforged.net" - "resources.download.minecraft.net" drasl: image: unmojang/drasl:latest container_name: drasl restart: unless-stopped # Internal-only: no published host port. Reached via Caddy at # auth.${BASE_DOMAIN} (Caddy holds the network alias on mcnet). volumes: - ./drasl/config:/etc/drasl:ro - drasl_state:/var/lib/drasl networks: - mcnet # No env vars — config is the rendered ./drasl/config/config.toml # (from config.toml.tmpl via tooling/render-config.sh). minecraft: image: itzg/minecraft-server:java21 container_name: minecraft restart: unless-stopped stdin_open: true tty: true ports: - "25565:25565" - "24454:24454/udp" # Simple Voice Chat, if used environment: EULA: "TRUE" TYPE: "NEOFORGE" VERSION: "1.21.1" # Verify NEOFORGE_VERSION against projects.neoforged.net before deploy. NEOFORGE_VERSION: "21.1.209" # pin a known-good build; update deliberately # ── Memory / performance ──────────────────────────────────── INIT_MEMORY: "4G" MAX_MEMORY: "10G" # 8 players + 100 mods comfortably USE_AIKAR_FLAGS: "TRUE" JVM_XX_OPTS: "-XX:+UseG1GC" # ── Auth: offline mode + authlib-injector pointing at Drasl ─ ONLINE_MODE: "FALSE" # mount authlib-injector.jar at /extras/authlib-injector.jar JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=http://auth.${BASE_DOMAIN}/authlib-injector" # ── Server config ─────────────────────────────────────────── DIFFICULTY: "normal" MODE: "survival" MOTD: "LAN party — kitchen sink" MAX_PLAYERS: "10" VIEW_DISTANCE: "10" SIMULATION_DISTANCE: "8" ENFORCE_SECURE_PROFILE: "FALSE" # required for authlib-injector on 1.21+ ALLOW_FLIGHT: "TRUE" # many tech mods need this # ── Mod source: packwiz pack served by caddy ──────────────── PACKWIZ_URL: "http://packwiz.${BASE_DOMAIN}/pack.toml" # ── RCON for remote admin ─────────────────────────────────── ENABLE_RCON: "TRUE" RCON_PASSWORD: ${RCON_PASSWORD} RCON_PORT: 25575 TZ: "Europe/Madrid" volumes: - mc_data:/data - ./runtime:/extras:ro # authlib-injector.jar lives here depends_on: - drasl - caddy # packwiz pack is served by caddy networks: - mcnet # Optional: backups mc-backup: image: itzg/mc-backup container_name: mc-backup restart: unless-stopped environment: BACKUP_INTERVAL: "6h" RCON_HOST: "minecraft" RCON_PASSWORD: ${RCON_PASSWORD} PRUNE_BACKUPS_DAYS: "14" TZ: "Europe/Madrid" volumes: - mc_data:/data:ro - ./backups:/backups depends_on: - minecraft networks: - mcnet volumes: drasl_state: mc_data: networks: mcnet: driver: bridge