Docker для Minecraft серверов: плюсы, минусы, подводные камни
Допустим, у вас три Minecraft-сервера: лобби, выживание и мини-игры. Каждый требует свою версию Java, свои плагины, свои настройки JVM. Обновляете один - ломаете другой. Знакомая история? Docker решает именно эту проблему. Каждый сервер живёт в изолированном контейнере со своим окружением, и они не мешают друг другу.
Но Docker - это не серебряная пуля. У него есть реальные ограничения, особенно для игровых серверов, где важна каждая миллисекунда. В этой статье разберём, когда Docker действительно полезен, а когда создаёт больше проблем, чем решает.
Зачем Docker для Minecraft
Основная идея Docker - изоляция. Каждый контейнер содержит всё необходимое для работы приложения: операционную систему, Java, сервер, плагины. Если контейнер упал, остальные продолжают работать. Если нужно обновить Java с 17 на 21 для одного сервера - это не затронет другие.
Воспроизводимость. Docker-образ содержит фиксированное окружение. То, что работает на вашей тестовой машине, будет работать точно так же на продакшене. Нет больше "а у меня всё работало" - если образ собрался, он запустится одинаково на любой машине с Docker.
Простота деплоя. Новый сервер - это один docker-compose up -d. Не нужно ставить Java, настраивать переменные окружения, скачивать jar-файлы вручную. Всё описано в docker-compose.yml, и любой член команды может развернуть идентичное окружение.
Изоляция ресурсов. Docker использует cgroups для ограничения CPU и памяти. Если сервер мини-игр внезапно съел всю память из-за утечки в плагине, он не утянет за собой лобби и выживание. OOM-киллер убьёт только один контейнер.
Простые бэкапы. Данные хранятся в Docker volumes или bind mounts. Бэкап - это копирование директории. Восстановление - замена директории и перезапуск контейнера. Никаких сложных процедур.
Образ itzg/minecraft-server
Не нужно писать свой Dockerfile с нуля. Образ itzg/minecraft-server - это де-факто стандарт для запуска MC в Docker. Он поддерживает Paper, Spigot, Fabric, Forge, Velocity, BungeeCord и десятки других серверных платформ.
Минимальный запуск:
docker run -d \
--name mc-server \
-p 25565:25565 \
-e EULA=TRUE \
-e TYPE=PAPER \
-e VERSION=1.21.4 \
-v mc-data:/data \
itzg/minecraft-server
Контейнер скачает Paper 1.21.4, примет EULA, создаст мир и начнёт слушать порт 25565. Данные сохранятся в volume mc-data.
Полезные переменные окружения:
MEMORY=4G- выделить 4 ГБ под JVM (устанавливает и -Xms, и -Xmx)TYPE=PAPER- тип сервера (PAPER, SPIGOT, FABRIC, FORGE, VELOCITY, BUNGEECORD)VERSION=1.21.4- версия MinecraftONLINE_MODE=FALSE- для серверов за прокси (Velocity/BungeeCord)OPS=player1,player2- список операторовDIFFICULTY=hard- сложностьVIEW_DISTANCE=10- дальность прорисовкиMAX_PLAYERS=100- лимит игроковJVM_OPTS=-XX:+UseG1GC -XX:+ParallelRefProcEnabled- дополнительные JVM-флаги
Docker Compose: один сервер
Для продакшена всегда используйте docker-compose вместо голого docker run. Конфиг в файле проще поддерживать, версионировать и передавать коллегам.
version: "3.8"
services:
minecraft:
image: itzg/minecraft-server:latest
container_name: mc-survival
restart: unless-stopped
environment:
EULA: "TRUE"
TYPE: PAPER
VERSION: "1.21.4"
MEMORY: "6G"
VIEW_DISTANCE: "12"
MAX_PLAYERS: "50"
DIFFICULTY: "hard"
SPAWN_PROTECTION: "0"
SNOOPER_ENABLED: "FALSE"
JVM_OPTS: >-
-XX:+UseG1GC
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=200
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
-XX:G1HeapRegionSize=8M
-XX:G1ReservePercent=20
-XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=15
-XX:G1MixedGCLiveThresholdPercent=90
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
volumes:
- ./server-data:/data
ports:
- "25565:25565"
deploy:
resources:
limits:
memory: 8G
cpus: "4.0"
reservations:
memory: 6G
cpus: "2.0"
Обратите внимание на deploy.resources. Здесь мы ставим жёсткие лимиты: контейнер не сможет использовать больше 8 ГБ RAM и 4 ядер CPU. При этом резервируем минимум 6 ГБ и 2 ядра. Лимит памяти Docker должен быть выше, чем MEMORY для JVM - Java потребляет больше, чем размер heap (метаспейс, нативная память, потоки).
Volumes: миры, плагины, конфиги
Правильная организация volumes - ключ к удобному управлению. У вас два варианта: Docker volumes и bind mounts.
Bind mounts (рекомендую для MC) - маппинг конкретной директории хоста в контейнер:
volumes:
- ./server-data:/data
Все файлы сервера будут в ./server-data на хосте. Можно редактировать конфиги напрямую, копировать плагины через scp, делать бэкапы обычным rsync.
Docker volumes - управляемые Docker'ом хранилища:
volumes:
- mc-data:/data
volumes:
mc-data:
Данные хранятся в /var/lib/docker/volumes/mc-data/_data. Более изолированно, но менее удобно для прямого доступа.
Для Minecraft серверов bind mounts практичнее. Типичная структура:
./server-data/
world/ # основной мир
world_nether/ # незер
world_the_end/ # энд
plugins/ # JAR-файлы и конфиги плагинов
server.properties
paper-global.yml
paper-world-defaults.yml
ops.json
whitelist.json
Чтобы добавить плагин, просто скопируйте JAR в ./server-data/plugins/ и перезапустите контейнер.
Сеть: host vs bridge
Это критически важный выбор для игровых серверов.
Bridge (по умолчанию). Docker создаёт виртуальную сеть. Трафик проходит через NAT. Для большинства веб-приложений это нормально. Для Minecraft - добавляет задержку. NAT обрабатывает каждый пакет, и при 100 игроках это ощутимо. Плюс: порт-форвардинг, изоляция сетей.
Host mode. Контейнер использует сетевой стек хоста напрямую. Нет NAT, нет оверхеда. Пакеты идут прямо в Java-процесс. Минус: нет сетевой изоляции, контейнер занимает порты хоста.
services:
minecraft:
image: itzg/minecraft-server:latest
network_mode: host
environment:
EULA: "TRUE"
TYPE: PAPER
SERVER_PORT: "25565"
Для игровых серверов используйте host mode. Разница в latency 1-3 мс может показаться мелочью, но при 20 тиках в секунду и десятках игроков она накапливается. Bridge mode оправдан, если вам нужна изоляция сетей между контейнерами или если на одной машине несколько серверов на разных портах.
Производительность: Java в контейнерах
Java в Docker работает хорошо, но есть нюансы.
Память. JVM видит лимиты cgroups и корректно определяет доступную память (в Java 10+ это из коробки). Если вы поставили MEMORY=6G и Docker-лимит 8G, всё будет корректно. Но не ставьте Docker-лимит равным размеру heap - JVM нужна память сверх heap (200-500 МБ на метаспейс, потоки, нативные буферы).
CPU. Docker использует CPU shares и CFS quota. По умолчанию контейнеры делят CPU пропорционально. Жёсткий лимит (cpus: "4.0") может привести к троттлингу - GC-пауза не сможет использовать больше 4 ядер, даже если они свободны. Для одиночного сервера лучше не ставить жёсткий лимит CPU, а использовать cpu_shares для приоритизации.
Диск. Overlay2 (стандартная файловая система Docker) добавляет минимальный оверхед при чтении. Но при активной записи (сохранение мира, логи) разница может быть заметна. Bind mounts работают без overlay - прямой доступ к файловой системе хоста. Ещё одна причина использовать bind mounts для данных.
Сеть. Про bridge vs host уже говорили. Важно: если используете bridge, включите jumbo frames на Docker-сети не получится - MTU фиксирован. При большом количестве чанков, летящих игроку, это может увеличить количество пакетов.
Мультисервер: Velocity + Paper
Реальная мощь Docker раскрывается при мультисерверном сетапе. Один docker-compose.yml описывает всю инфраструктуру.
version: "3.8"
services:
proxy:
image: itzg/bungeecord:latest
container_name: mc-velocity
restart: unless-stopped
environment:
TYPE: VELOCITY
MEMORY: "512M"
volumes:
- ./velocity-data:/server
ports:
- "25565:25577"
deploy:
resources:
limits:
memory: 1G
lobby:
image: itzg/minecraft-server:latest
container_name: mc-lobby
restart: unless-stopped
environment:
EULA: "TRUE"
TYPE: PAPER
VERSION: "1.21.4"
MEMORY: "2G"
ONLINE_MODE: "FALSE"
SERVER_PORT: "25566"
volumes:
- ./lobby-data:/data
expose:
- "25566"
deploy:
resources:
limits:
memory: 3G
survival:
image: itzg/minecraft-server:latest
container_name: mc-survival
restart: unless-stopped
environment:
EULA: "TRUE"
TYPE: PAPER
VERSION: "1.21.4"
MEMORY: "6G"
ONLINE_MODE: "FALSE"
SERVER_PORT: "25567"
volumes:
- ./survival-data:/data
expose:
- "25567"
deploy:
resources:
limits:
memory: 8G
minigames:
image: itzg/minecraft-server:latest
container_name: mc-minigames
restart: unless-stopped
environment:
EULA: "TRUE"
TYPE: PAPER
VERSION: "1.21.4"
MEMORY: "4G"
ONLINE_MODE: "FALSE"
SERVER_PORT: "25568"
volumes:
- ./minigames-data:/data
expose:
- "25568"
deploy:
resources:
limits:
memory: 6G
Обратите внимание: Paper-серверы используют expose вместо ports - они не доступны извне, только через Velocity. Прокси публикует порт 25565, принимает подключения и маршрутизирует на внутренние серверы по Docker DNS (имена сервисов: lobby, survival, minigames).
В velocity.toml серверы указываются по имени Docker-сервиса:
[servers]
lobby = "lobby:25566"
survival = "survival:25567"
minigames = "minigames:25568"
try = ["lobby"]
Docker автоматически резолвит lobby в IP контейнера. Если контейнер перезапустится и получит новый IP, DNS обновится.
Бэкапы Docker-серверов
Бэкап сервера в Docker - это бэкап bind mount директории. Но есть важный момент: нельзя копировать файлы мира, пока сервер пишет в них.
Правильный подход:
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups/minecraft"
DATE=$(date +%Y%m%d_%H%M%S)
# Отключаем автосохранение и делаем финальный save
docker exec mc-survival rcon-cli save-off
docker exec mc-survival rcon-cli save-all
sleep 5
# Копируем данные
tar czf "$BACKUP_DIR/survival-$DATE.tar.gz" ./survival-data/
# Включаем автосохранение обратно
docker exec mc-survival rcon-cli save-on
echo "Backup completed: survival-$DATE.tar.gz"
Образ itzg/minecraft-server включает rcon-cli - можно выполнять серверные команды без подключения к консоли контейнера.
Для автоматизации - cron:
0 */4 * * * /opt/minecraft/backup.sh >> /var/log/mc-backup.log 2>&1
Бэкап каждые 4 часа. Для крупных серверов рассмотрите инкрементальные бэкапы через borgbackup.
Безопасность
Docker-контейнеры по умолчанию запускаются от root внутри контейнера. Для Minecraft-сервера это не критично (контейнер изолирован), но лучше перестраховаться.
Непривилегированный пользователь. Образ itzg/minecraft-server поддерживает UID/GID:
environment:
UID: 1000
GID: 1000
Read-only файловая система. Можно сделать корневую ФС контейнера read-only, оставив write-доступ только для данных:
services:
minecraft:
image: itzg/minecraft-server:latest
read_only: true
tmpfs:
- /tmp
volumes:
- ./server-data:/data
Ограничение capabilities. Убираем лишние Linux capabilities:
services:
minecraft:
image: itzg/minecraft-server:latest
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
Не используйте --privileged. Никогда. Это даёт контейнеру полный доступ к хосту.
Обновляйте образы. docker-compose pull && docker-compose up -d - обновит образы и пересоздаст контейнеры с новыми версиями.
Когда Docker не нужен
Docker - не универсальное решение. Вот ситуации, когда он добавляет сложности без пользы:
Один сервер на выделенной машине. Если у вас одна VPS с одним Minecraft-сервером, Docker даёт минимум преимуществ. Java и так изолирована в JVM, а Docker добавляет слой абстракции. Проще установить Java напрямую и запускать через systemd.
Критичная производительность. Если вы выжимаете каждый тик из сервера на 200+ игроков, любой оверхед имеет значение. Docker на bridge-сети добавляет latency. Overlay FS добавляет задержку при записи. cgroups добавляют CPU scheduling overhead. На host mode с bind mounts разница минимальна, но она есть.
Отсутствие DevOps-опыта. Если вы не знакомы с Docker, отладка проблем внутри контейнера будет сложнее, чем на голой машине. Логи в другом месте, файловая система многослойная, сеть виртуальная. Начните с изучения Docker на менее критичных проектах.
Shared-хостинг. На панелях типа Pterodactyl Docker уже используется под капотом. Добавлять ещё один слой Docker поверх не имеет смысла.
Полезные команды
Быстрый справочник для ежедневной работы:
# Статус контейнеров
docker-compose ps
# Логи сервера (последние 100 строк, в реальном времени)
docker-compose logs -f --tail=100 survival
# Консоль сервера через rcon
docker exec mc-survival rcon-cli
# Перезапуск одного сервера без остановки остальных
docker-compose restart survival
# Обновление образов
docker-compose pull
docker-compose up -d
# Использование ресурсов
docker stats
# Копирование плагина в контейнер
cp my-plugin.jar ./survival-data/plugins/
docker-compose restart survival
Итого
Docker отлично подходит для мультисерверных сетапов, тестовых окружений и ситуаций, где важна воспроизводимость. Образ itzg/minecraft-server покрывает 95% потребностей. Host networking убирает сетевой оверхед, bind mounts дают прямой доступ к файлам, а cgroups защищают от утечек памяти в плагинах.
Но если у вас один сервер на одной машине - обычный systemd-сервис будет проще и немного производительнее. Docker решает проблемы масштабирования и воспроизводимости. Если этих проблем нет, решение ищет проблему.
Начните с docker-compose для тестового сервера. Погоняйте его неделю, посмотрите на производительность, привыкните к воркфлоу. Если всё устраивает - мигрируйте продакшен. Если нет - ничего не потеряете, файлы мира можно перенести обратно на голый сервер за пару минут.
Protege tu servidor contra ataques DDoS
Protección gratuita con configuración en 5 minutos. 1 TB de tráfico incluido.
Probar gratisArtículos relacionados
Velocity SMP-сеть: лобби, survival, creative и minigames на одном прокси
Как поднять мульти-серверную SMP-сеть на Velocity: настройка velocity.toml, modern forwarding, синхронизация прав и чата между лобби, survival и creative.
Lifesteal SMP сервер: как создать и настроить с нуля (2026)
Lifesteal SMP - самый хайповый режим на Minecraft YouTube. Убил игрока - забрал его сердце. 0 сердец - перманентный бан. Разбираем плагины, конфиги и правила для своего сервера.
Мой Minecraft сервер ддосят: что делать прямо сейчас
Пошаговый план действий, если ваш Minecraft сервер прямо сейчас под DDoS-атакой. Как определить тип атаки, что делать в первые минуты и как защититься, чтобы это не повторилось.