强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PaperMC 插件开发完全指南 / 第 15 章:Docker 开发环境

第 15 章:Docker 开发环境

使用 Docker 搭建一致的开发、测试和 CI/CD 环境。


15.1 Docker 在 Minecraft 开发中的价值

维度传统方式Docker 方式
环境一致性手动配置,容易不一致镜像保证完全一致
多版本测试安装多个 JDK/服务器同时运行多个容器
清理环境手动删除文件docker compose down -v
CI/CD需要配置环境直接使用镜像
团队协作“在我电脑上能跑”统一的开发环境

15.2 基础 Dockerfile

Paper 服务器 Dockerfile

# Dockerfile
FROM eclipse-temurin:21-jre-alpine

# 安装必要工具
RUN apk add --no-cache curl bash

# 创建用户(非 root 运行更安全)
RUN addgroup -S minecraft && adduser -S minecraft -G minecraft

# 设置工作目录
WORKDIR /server

# 下载 Paper(构建时指定版本)
ARG PAPER_VERSION=1.21.4
ARG BUILD_NUMBER=123

RUN curl -o paper.jar \
    "https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${BUILD_NUMBER}/downloads/paper-${PAPER_VERSION}-${BUILD_NUMBER}.jar"

# 同意 EULA
RUN echo "eula=true" > eula.txt

# 默认配置
COPY server.properties /server/server.properties

# 设置权限
RUN chown -R minecraft:minecraft /server

# 切换到非 root 用户
USER minecraft

# 暴露端口
EXPOSE 25565

# JVM 参数
ENV JAVA_OPTS="-Xms512M -Xmax2G -XX:+UseG1GC"

# 启动命令
CMD ["bash", "-c", "java $JAVA_OPTS -jar paper.jar --nogui"]

15.3 Docker Compose 配置

开发服务器配置

# docker-compose.yml
version: '3.8'

services:
  minecraft:
    build: .
    container_name: mc-dev-server
    ports:
      - "25565:25565"
    volumes:
      # 持久化服务器数据
      - ./server-data:/server
      # 挂载插件目录(热重载)
      - ./target:/server/plugins:ro
      # 挂载配置
      - ./config/server.properties:/server/server.properties
    environment:
      - JAVA_OPTS=-Xms1G -Xmax2G -XX:+UseG1GC
    restart: unless-stopped
    stdin_open: true
    tty: true

  # 多服务器网络(可选)
  proxy:
    image: itzg/bungeecord:latest
    container_name: mc-proxy
    ports:
      - "25577:25577"
    volumes:
      - ./proxy-data:/config
    depends_on:
      - minecraft
    restart: unless-stopped

启动脚本

#!/bin/bash
# start-dev.sh

# 构建插件
mvn clean package -f pom.xml

# 复制插件到服务器
cp target/my-plugin-1.0.0.jar ./target/

# 启动服务器
docker compose up -d

# 查看日志
docker compose logs -f minecraft

15.4 开发工作流

热重载工作流

# docker-compose.dev.yml
version: '3.8'

services:
  minecraft:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      # 挂载构建输出目录(文件变化自动同步)
      - ./target:/server/plugins
      # 挂载配置文件
      - ./src/main/resources:/server/config
    environment:
      - JAVA_TOOL_OPTIONS=-XX:+UseG1GC
    stdin_open: true
    tty: true

自动重载脚本

#!/bin/bash
# watch-and-reload.sh

# 监视文件变化并重新构建
while true; do
    inotifywait -e modify -r src/
    echo "文件变化,重新构建..."
    mvn clean package -q
    echo "插件已更新,请在服务器中执行 /reload"
done

15.5 多版本测试

测试多个 Minecraft 版本

# docker-compose.test.yml
version: '3.8'

services:
  mc-1.21.4:
    image: itzg/minecraft-server:java21
    container_name: mc-1.21.4
    environment:
      EULA: "TRUE"
      TYPE: "PAPER"
      VERSION: "1.21.4"
      MEMORY: "1G"
    ports:
      - "25566:25565"
    volumes:
      - ./plugins-1.21:/data/plugins

  mc-1.20.6:
    image: itzg/minecraft-server:java21
    container_name: mc-1.20.6
    environment:
      EULA: "TRUE"
      TYPE: "PAPER"
      VERSION: "1.20.6"
      MEMORY: "1G"
    ports:
      - "25567:25565"
    volumes:
      - ./plugins-1.20:/data/plugins

15.6 CI/CD 集成

GitHub Actions 工作流

# .github/workflows/build-and-test.yml
name: Build and Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      - name: Build with Maven
        run: mvn clean package

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: my-plugin
          path: target/*.jar

  integration-test:
    needs: build
    runs-on: ubuntu-latest

    services:
      minecraft:
        image: itzg/minecraft-server:java21
        ports:
          - 25565:25565
        env:
          EULA: "TRUE"
          TYPE: "PAPER"
          VERSION: "1.21.4"
          MEMORY: "1G"

    steps:
      - uses: actions/checkout@v4

      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: my-plugin

      - name: Copy plugin to server
        run: |
          sleep 30  # 等待服务器启动
          docker cp target/my-plugin-1.0.0.jar $(docker ps -q --filter "ancestor=itzg/minecraft-server"):/data/plugins/

      - name: Run integration tests
        run: |
          # 运行集成测试脚本
          python3 tests/integration/test_server.py

简化版 CI(Maven 构建)

# .github/workflows/build.yml
name: Build

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - run: mvn clean package
      - uses: actions/upload-artifact@v4
        with:
          name: plugin-jar
          path: target/*.jar

15.7 生产环境部署

生产 Dockerfile

FROM eclipse-temurin:21-jre-alpine

RUN apk add --no-cache curl

ARG PAPER_VERSION=1.21.4
ARG BUILD_NUMBER=123

WORKDIR /server

# 下载 Paper
RUN curl -o paper.jar \
    "https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${BUILD_NUMBER}/downloads/paper-${PAPER_VERSION}-${BUILD_NUMBER}.jar"

# 接受 EULA
RUN echo "eula=true" > eula.txt

# 复制服务器配置
COPY server.properties .
COPY spigot.yml .
COPY paper-global.yml ./config/

# 创建数据目录
RUN mkdir -p /data

# 使用卷挂载数据
VOLUME ["/data"]

# 暴露端口
EXPOSE 25565

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s \
    CMD mc-health || exit 1

CMD ["java", "-Xms2G", "-Xmx4G", "-XX:+UseG1GC", "-jar", "paper.jar", "--nogui", "--plugins=/plugins"]

生产 docker-compose.yml

version: '3.8'

services:
  minecraft:
    build: .
    container_name: mc-production
    ports:
      - "25565:25565"
    volumes:
      - mc-data:/data
      - ./plugins:/plugins:ro
    environment:
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          memory: 6G
          cpus: '2.0'
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  mc-data:

15.8 备份策略

自动备份脚本

#!/bin/bash
# backup.sh

BACKUP_DIR="/backups"
SERVER_DIR="/var/lib/minecraft"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="mc_backup_$DATE"

# 通知服务器正在备份
docker exec mc-production rcon-cli "say §e正在备份服务器..."

# 创建备份
mkdir -p $BACKUP_DIR
tar -czf "$BACKUP_DIR/$BACKUP_NAME.tar.gz" -C $SERVER_DIR .

# 保留最近 7 天的备份
find $BACKUP_DIR -name "mc_backup_*.tar.gz" -mtime +7 -delete

# 通知完成
docker exec mc-production rcon-cli "say §a备份完成!"

echo "备份完成: $BACKUP_NAME"

定时备份(crontab)

# crontab -e
# 每天凌晨 3 点备份
0 3 * * * /opt/minecraft/backup.sh >> /var/log/mc-backup.log 2>&1

15.9 RCON 管理

启用 RCON

# server.properties
enable-rcon=true
rcon.port=25575
rcon.password=your_secure_password

使用 RCON 发送命令

# 安装 mcrcon
# https://github.com/Tiiffi/mcrcon

# 连接
mcrcon -H localhost -P 25575 -p your_secure_password

# 执行命令
mcrcon -H localhost -P 25575 -p your_secure_password "say Hello World"

Docker 中使用 RCON

services:
  minecraft:
    # ...其他配置
    ports:
      - "25565:25565"
      - "25575:25575"  # RCON 端口

15.10 监控

Prometheus + Grafana 监控

# docker-compose.monitoring.yml
services:
  minecraft:
    # ...主配置

  # Minecraft 导出器
  mc-exporter:
    image: samuelkadolph/minecraft-prometheus-exporter
    environment:
      - MINECRAFT_HOST=minecraft
      - MINECRAFT_PORT=25565
    ports:
      - "9225:9225"

  # Prometheus
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  # Grafana
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"

15.11 常见问题排查

问题原因解决方案
容器无法启动内存不足增加 Docker 内存限制
插件未加载路径挂载错误检查卷映射
性能差默认 JVM 参数不佳自定义 JAVA_OPTS
无法连接端口未映射检查 ports 配置
数据丢失未使用持久化卷使用 volumes 挂载

15.12 扩展阅读


15.13 本章小结

要点内容
Docker 优势环境一致、易于部署、快速清理
基础镜像eclipse-temurin:21-jre-alpine
持久化使用 volumes 保存服务器数据
CI/CDGitHub Actions 自动构建和测试
生产部署资源限制、健康检查、自动重启
备份定时备份脚本 + 持久化存储

下一章: 第 16 章:测试 — 学习单元测试、MockBukkit 和集成测试。