Not every Minecraft mod got ported forward. When the mod you want only exists for a Java Forge build from 2014, running it means a dedicated legacy server. Not an update to whatever you already host. The Bedrock and modern Fabric guides cover the other two flavours this homelab runs side by side. Here is what broke getting a 1.7.10 Forge server running in Docker, and a real rescue mission when a player got stuck somewhere the mod never expected anyone to survive.
Bedrock and Java Edition are different games under the hood. Different engine, different codebase, no shared mod ecosystem. A mod built for Java Forge cannot run on a Bedrock server, however the Bedrock side is configured. If the mod you want only exists for Java and your existing server is Bedrock, the only real option is a second, dedicated Java server just for it.
itzg/minecraft-server is the well-known Docker image for running Minecraft servers. Use it
without pinning a version tag and you get whatever JDK the image currently defaults to, which on a modern
pull is far newer than Java 8. Forge's LaunchWrapper for 1.7.10 hard-casts the JVM's system
classloader to java.net.URLClassLoader:
java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader
cannot be cast to class java.net.URLClassLoader
That cast only works on Java 8. The classloader model changed with the Java 9 module system, and nothing
about that is fixable at the Forge-version level. It is a hard incompatibility. Fix: pin the image
tag explicitly to java8, not the default or latest tag. Any Forge version below roughly
1.18 needs that tag, whatever the base image currently defaults to. That default has a way of quietly moving
forward, so do not assume last year's compose file still pulls Java 8 today.
services:
mc:
image: itzg/minecraft-server:java8
container_name: mc-java
ports:
- "25566:25565"
environment:
EULA: "TRUE"
TYPE: "FORGE"
VERSION: "1.7.10"
MEMORY: "4G"
MOTD: "Homelab Java Server"
volumes:
- ./data:/data
restart: unless-stopped
stdin_open: true
tty: true
The image's own auto-installer, TYPE: FORGE with VERSION: 1.7.10, handled the Forge
install cleanly on this run. Know this going in. Legacy Forge installers on this image have a documented
history of flaking on old vanilla-jar downloads. HTTP 403s from Mojang's legacy asset hosting, install-time
NullPointerExceptions. Nothing hit here, but a plausible failure mode for other old Forge versions. Retry
before you assume something is fundamentally broken.
CurseForge now requires an API key for their official API, and blocks straightforward scraping of a mod's
download page. Hit it directly and you get an HTML page, not the jar file, even with a normal browser
user-agent. The CDN's direct file path still works with no authentication, once you know a mod file's numeric
file ID, which is visible on the mod's CurseForge page. Split the ID as
{fileID // 1000}/{fileID % 1000}:
https://edge.forgecdn.net/files/5737/543/ExampleMod-1.7.10-2.4.0.jar
File ID 5737543 becomes 5737/543. Download that directly, drop it into the mods
folder bound to the container's /data/mods, and restart the container. Then confirm in the
startup log that the mod loaded. Forge Mod Loader lists every mod it identified on boot. A silently missing
mod jar looks identical to a successfully started vanilla server, until someone checks the log or tries to
use the mod's content in-game.
A Forge mod's custom dimension is a one-way trip when the mod's author never built an exit. That happened here for real. A player got dropped into a mod-added pocket dimension by one of the mod's own abilities, with no in-mod portal, item or command to get back. Getting them out took several wrong turns, on a Minecraft version old enough that some assumptions about how commands behave do not hold:
gamerule keepInventory true before you force any death. It defaults to
false, so a careless kill drops the player's entire inventory somewhere they cannot get back
to. Set it to false again immediately after. The second half of this step is easy to
forget./kill <player> from the console silently fails on 1.7.10. No error,
no exception logged. The RCON connection resets before the command executes. 1.7.10's /kill
takes no arguments at all. Multiplayer-target support came in a later Minecraft version. Passing
an argument a command does not expect broke the RCON round-trip silently rather than erroring cleanly.
Remember that as a class of failure, not a one-off./kill themselves, with no argument, failed on a
permission error. /kill defaults to operator level 2, and the player was not opped./effect Instant Damage failed too. Silently, twice,
even at a very high amplifier. The server's default game mode was Creative, and Creative mode is
fully immune to potion-effect damage at any amount. No error. The effect just does nothing to a
Creative-mode player./op the player. Have them run bare
/kill on themselves once opped. Then /deop them immediately. Confirmed via the
server log's own death message, "fell out of the world", the vanilla message for the special world-boundary
damage source /kill applies internally. That is also why self-/kill succeeds in
Creative mode when a normal damage effect never can. It is not routed through the normal damage system at
all. Restore keepInventory to its original value immediately after, and confirm the gamerule
reads back the value you expect.
The lesson generalizes past this one mod. On an old legacy modded server, do not assume a command that
"should" work on a modern server behaves the same way. Check /help <command> for the real
argument signature this version supports. And treat a command that silently resets an RCON connection with no
logged error as a malformed command for this version. Not a transient connectivity problem worth retrying
blindly.