setupmc.com

Minecraft Container Keeps Restarting: Diagnose the Docker Compose Loop

Find out why an itzg/minecraft-server container repeatedly restarts. This runbook preserves the first error, separates exited from unhealthy containers, and fixes the actual cause.

Docker Operations
setupmc.com Team

Need a cleaner Java baseline?

Generate a Java Compose setup before you keep patching by hand

If you are still refining the Java server baseline, use the Java configurator and then return to the guides for the next issue.

Open Java configurator

What a restart loop actually means

With restart: unless-stopped, Docker starts the same container again after its main process terminates. The restart policy is exposing a failure; it is not the cause.

An unhealthy status is different. The itzg image checks the Minecraft server with mc-health, but a normal Docker restart policy does not restart a container merely because its healthcheck fails. If that happens, look for an auto-heal container, Portainer rule, or orchestrator.

Stop the loop without deleting evidence

Do not begin with docker compose down, volume deletion, or a fresh /data. Disable the runtime restart policy on the existing container and let its current failing start end naturally:

docker compose ps -a
mc_container_id="$(docker compose ps -a -q mc)"
docker update --restart=no "$mc_container_id"
docker wait "$mc_container_id"

docker wait prints the exit code. This avoids sending a new stop signal that could replace the failure state you are trying to inspect. The production policy remains in Compose and returns when the service is recreated.

Capture exit state and the first error

Inspect the preserved last state:

docker inspect "$mc_container_id" --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{json .State.Error}} restarts={{.RestartCount}}'
docker compose logs --tail=250 --timestamps mc

Read from the top of the failing startup. The final lines often contain only shutdown noise caused by the earlier error.

Useful interpretations:

ResultMeaningNext check
Exit 1Initialization or application errorFirst ERROR, Exception, or [init] failure
Exit 137Process received SIGKILLCheck OOMKilled; use the Exit 137 runbook
Exit 143Process received SIGTERMLook for an operator, deployment, or stop timeout
Running but unhealthyProcess still exists, health probe failsInspect health output and server readiness

Inspect the healthcheck history when the container did not exit:

docker inspect "$mc_container_id" --format '{{json .State.Health}}'
docker compose logs --tail=250 mc

Validate the effective Compose configuration

Many loops come from editing a file that Compose is not actually using:

docker compose config
docker compose images
docker image inspect "$(docker compose images -q mc)" --format '{{json .Config.Labels}}'

Confirm the effective image, TYPE, VERSION, Java tag, volumes, and memory settings. docker compose restart does not apply changed environment variables; recreation does.

Match the first error to the cause

First useful errorLikely causeFix
EULA is not acceptedEULA missing or not TRUECorrect Compose and recreate the service
Unsupported class-file or Java errorWrong Java image for Minecraft/modpackSelect the required Java tag
Permission denied under /dataHost ownership differs from container UID/GIDFollow the permissions runbook
No matching server/modpack versionInvalid TYPE, version, project, or file IDPin a compatible release from the primary source
Plugin/mod loading exceptionIncompatible or duplicated artifactRestore the last working set while stopped
Cannot reserve memoryHeap exceeds available container/host memoryReduce heap or increase verified capacity
Port already allocatedAnother container/process owns the host portIdentify the owner; do not change ports blindly

For noisy image initialization, temporarily add:

environment:
  DEBUG: "TRUE"
  DEBUG_EXEC: "TRUE"

Use DEBUG_MEMORY: "TRUE" only for JVM allocation failures. Remove debug flags after diagnosis because they make logs much larger and can expose more configuration detail.

Apply and verify the correction

After editing Compose, recreate rather than restart:

docker compose config
docker compose up -d --force-recreate mc
docker compose logs -f mc

Wait for the normal ready message, then verify stability instead of declaring success after one minute:

docker compose exec mc rcon-cli version
docker compose exec mc rcon-cli save-all flush
docker inspect "$(docker compose ps -q mc)" --format 'restarts={{.RestartCount}} health={{.State.Health.Status}}'

Repeat the last command after several minutes. The restart count must stay unchanged.

Do not hide the failure

Avoid these non-fixes:

  • removing the restart policy and assuming the server is repaired
  • deleting /data before preserving the failing state
  • increasing every timeout without reading the first error
  • disabling the image healthcheck to conceal a server that never becomes ready
  • repeatedly pulling latest and changing the failure surface

Next steps

Frequently asked questions

Short answers to the questions that usually come up while working through this topic.