A production-oriented Docker image for running a Minecraft Java server as a non-root user, with the server JAR downloaded at container startup.
The recommended way to run this image is with Docker Compose. Compose keeps the server configuration, volumes, ports, restart policy, resource limits, hardening options, logging, healthchecks, and JVM tuning in one reviewable file.
- Runs as a dedicated non-root user.
- Downloads
server.jarfromJAR_URLat startup with retry support. - Supports optional checksum verification via
JAR_SHA256. - Stores all mutable server state under
/data. - Works for Minecraft Java servers and proxy jars such as Velocity.
- Includes production-ready Docker Compose examples for single-server, internal-only, proxy, multi-server, legacy/private, and external-network deployments.
mkdir -p ./data/serverAll world data, plugins, configuration files, logs, and downloaded jars should live under /data inside the container.
name: minecraft-single-server
x-aikar-java-args: &aikar-java-args >-
-XX:+UseG1GC
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=200
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:+AlwaysPreTouch
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
-XX:G1HeapRegionSize=8M
-XX:G1ReservePercent=20
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=15
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
x-minecraft-common: &minecraft-common
image: ghcr.io/hauntedmc/mcserver:latest
restart: unless-stopped
networks:
- minecraft
read_only: true
tmpfs:
- /tmp:rw,exec,size=128m
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 1024
stdin_open: true
tty: true
stop_grace_period: 120s
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
x-healthcheck-25565: &healthcheck-25565
test: ["CMD-SHELL", "grep -q ':63DD .* 0A ' /proc/net/tcp /proc/net/tcp6 2>/dev/null"]
interval: 30s
timeout: 5s
retries: 3
start_period: 180s
services:
server:
<<: *minecraft-common
container_name: minecraft-server
mem_limit: 8G
mem_reservation: 6G
environment:
TZ: Europe/Amsterdam
JVM_MEMORY: 6G
JAVA_ARGS: *aikar-java-args
MC_NOGUI: "true"
JAR_URL: "https://example.com/server.jar"
JAR_SHA256: ""
JAR_DOWNLOAD_MODE: always
ports:
- "25565:25565/tcp"
- "25565:25565/udp"
healthcheck: *healthcheck-25565
volumes:
- type: bind
source: ./data/server
target: /data
networks:
minecraft:
name: minecraftSet JAR_URL to the server jar you want to run. Set JAR_SHA256 when you want startup checksum verification.
docker compose up -d
docker compose logs -f serverStop it with:
docker compose downTo keep the server internal-only on a Docker network, use examples/compose.internal-hardened.yaml or remove the ports section.
The quick-start example above is also available as examples/compose.single-server.yaml.
Additional complete Compose examples live in examples/:
| File | Use case |
|---|---|
compose.internal-hardened.yaml |
Hardened Minecraft server with no host ports published. Use behind a proxy or from another container on the same Docker network. |
compose.proxy-backend.yaml |
Velocity-style proxy with a private backend server. Includes proxy-specific JVM flags and backend Aikar flags. |
compose.multi-server-profiles.yaml |
Production-style multi-server layout using anchors and Compose profiles for prod, dev, private, and event. |
compose.external-network.yaml |
External Docker network with fixed container IPs, useful when the network is managed outside this Compose project. |
Copy one of the examples into compose.yaml, then adjust paths, memory limits, published IPs, published ports, and jar URLs.
cp examples/compose.proxy-backend.yaml compose.yaml
docker compose up -dFor profile-based examples:
docker compose --profile prod up -d
docker compose --profile dev up -d
docker compose --profile private up -d
docker compose --profile event up -dFor proxy deployments, set MC_NOGUI=false because Velocity does not accept the --nogui startup argument.
| Variable | Required | Example | Description |
|---|---|---|---|
JAR_URL |
Yes | https://example.com/server.jar |
URL downloaded as server.jar when the container starts. |
JAR_SHA256 |
No | 0123...abcd |
Optional SHA-256 checksum used to verify the downloaded jar. Leave empty to skip checksum verification. |
JAR_DOWNLOAD_MODE |
No | always |
Controls jar download behavior. Use the value that matches your deployment workflow. |
JVM_MEMORY |
No | 4G |
Heap size passed to Java. Keep this lower than the container memory limit. |
JAVA_ARGS |
No | -XX:+UseG1GC |
Extra JVM flags. The examples use Aikar flags for Minecraft servers and a smaller proxy flag set for Velocity-style proxies. |
MC_NOGUI |
No | true |
Set to false for proxy jars that do not accept --nogui, such as Velocity. |
TZ |
No | Europe/Amsterdam |
Container timezone. |
The examples intentionally include the production-oriented settings from the HauntedMC deployment pattern:
read_only: truewith/datamounted as the writable state directory./tmpas writabletmpfswithexecenabled for Java/runtime temporary files.cap_drop: [ALL]andno-new-privileges:true.pids_limitto reduce runaway process risk.mem_limit,mem_reservation, andJVM_MEMORYtuned so the Java heap stays below the container limit.- JSON log rotation.
- TCP healthchecks that verify the expected Minecraft/proxy port is listening.
- Longer
stop_grace_periodvalues so the server has time to save worlds cleanly.
# Start all services without profiles
docker compose up -d
# Start a profile
docker compose --profile prod up -d
# Follow logs for one service
docker compose logs -f server
# Restart one service
docker compose restart server
# Stop and remove containers, keeping bind-mounted data
docker compose down
# Pull a new image and recreate services
docker compose pull
docker compose up -dDocker Compose is recommended for most deployments. For a quick manual test, you can still run the image directly:
docker run \
--name minecraft-server \
--restart unless-stopped \
-d \
-p 25565:25565/tcp \
-p 25565:25565/udp \
-e JVM_MEMORY=4G \
-e MC_NOGUI=true \
-e JAR_URL='https://example.com/path/to/server.jar' \
-v "$PWD/data:/data" \
ghcr.io/hauntedmc/mcserver:latestTo keep the server internal-only on Docker network, omit the -p flags.
The repository still includes shell runner examples for users who prefer script-based deployments:
- Minecraft server runner:
examples/run-minecraft-server.sh - Proxy server runner:
examples/run-proxy-server.sh
- Configuration reference: docs/configuration.md
- Operations guide: docs/operations.md
- Runtime and script reference: docs/runtime-reference.md
- Project structure and naming conventions: docs/project-structure.md
Run repository checks:
./scripts/validate.sh --with-docker-buildBuild locally:
./build.shBump release version and create tag:
./update_version.sh patchPushing a version tag such as v1.2.3 publishes:
ghcr.io/hauntedmc/mcserver:latestghcr.io/hauntedmc/mcserver:v1.2.3ghcr.io/hauntedmc/mcserver:1.2.3ghcr.io/hauntedmc/mcserver:sha-<commit>
- Contributing: CONTRIBUTING.md
- Security policy: SECURITY.md
- Code of conduct: CODE_OF_CONDUCT.md
- License: LICENSE