Skip to content

HauntedMC/mcserver

mcserver

CI Publish License: AGPL v3 Issues Pull Requests

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.

Highlights

  • Runs as a dedicated non-root user.
  • Downloads server.jar from JAR_URL at 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.

Quick start

1. Create a data directory

mkdir -p ./data/server

All world data, plugins, configuration files, logs, and downloaded jars should live under /data inside the container.

2. Create compose.yaml

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: minecraft

Set JAR_URL to the server jar you want to run. Set JAR_SHA256 when you want startup checksum verification.

3. Start the server

docker compose up -d
docker compose logs -f server

Stop it with:

docker compose down

To keep the server internal-only on a Docker network, use examples/compose.internal-hardened.yaml or remove the ports section.

Docker Compose examples

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 -d

For 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 -d

For proxy deployments, set MC_NOGUI=false because Velocity does not accept the --nogui startup argument.

Common environment variables

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.

Compose settings used by the examples

The examples intentionally include the production-oriented settings from the HauntedMC deployment pattern:

  • read_only: true with /data mounted as the writable state directory.
  • /tmp as writable tmpfs with exec enabled for Java/runtime temporary files.
  • cap_drop: [ALL] and no-new-privileges:true.
  • pids_limit to reduce runaway process risk.
  • mem_limit, mem_reservation, and JVM_MEMORY tuned 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_period values so the server has time to save worlds cleanly.

Useful Compose commands

# 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 -d

Docker run fallback

Docker 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:latest

To keep the server internal-only on Docker network, omit the -p flags.

Legacy runner scripts

The repository still includes shell runner examples for users who prefer script-based deployments:

Documentation

Development

Run repository checks:

./scripts/validate.sh --with-docker-build

Build locally:

./build.sh

Bump release version and create tag:

./update_version.sh patch

Release tags

Pushing a version tag such as v1.2.3 publishes:

  • ghcr.io/hauntedmc/mcserver:latest
  • ghcr.io/hauntedmc/mcserver:v1.2.3
  • ghcr.io/hauntedmc/mcserver:1.2.3
  • ghcr.io/hauntedmc/mcserver:sha-<commit>

Community and policy

About

Minecraft server image

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors