Skip to content

PaperMCServer

The PaperMCServer CRD defines a Minecraft server instance managed by the operator.

Overview

apiVersion: mc.k8s.lex.la/v1beta1
kind: PaperMCServer
metadata:
  name: my-server
  namespace: minecraft
spec:
  updateStrategy: "auto"
  # ... configuration

Spec Fields

updateStrategy

Required — Defines how Paper version updates are handled.

Value Description
latest Always use newest Paper version from Docker Hub (ignores plugin compatibility)
auto Constraint solver picks best version compatible with all plugins
pin Stay on specific version, auto-update to latest build
build-pin Fully pinned version and build, no automatic updates
spec:
  updateStrategy: "auto"  # Recommended for production

See Update Strategies for detailed guide.

version

Optional — Target Paper version. Required for pin and build-pin strategies.

spec:
  updateStrategy: "pin"
  version: "1.21.1"  # Pinned Minecraft version

build

Optional — Target Paper build number.

  • With pin strategy: minimum build (operator will still auto-update to newer builds)
  • With build-pin strategy: exact build (no automatic updates)
spec:
  updateStrategy: "build-pin"
  version: "1.21.1"
  build: 125  # Exact build number

updateDelay

Optional — Grace period before applying Paper updates. Useful for waiting for community feedback on new releases.

spec:
  updateDelay: "168h"  # Wait 7 days before applying updates

updateSchedule

Required — Defines when to check and apply updates.

Field Description
checkCron Cron expression for checking updates
maintenanceWindow.enabled Enable scheduled updates
maintenanceWindow.cron Cron expression for applying updates
spec:
  updateSchedule:
    checkCron: "0 3 * * *"           # Check daily at 3am
    maintenanceWindow:
      enabled: true
      cron: "0 4 * * 0"              # Apply updates Sunday 4am

Cron Format

Standard cron format: minute hour day-of-month month day-of-week

gracefulShutdown

Required — Configures graceful server shutdown.

Field Description
timeout Shutdown timeout (should match terminationGracePeriodSeconds)
spec:
  gracefulShutdown:
    timeout: 300s  # 5 minutes for world save

Timeout Configuration

The timeout should match your StatefulSet's terminationGracePeriodSeconds. Too short may cause world corruption. Minimum recommended: 300s.

rcon

Required — Configures RCON for graceful shutdown commands.

Field Description Default
enabled Enable RCON
passwordSecret.name Secret name
passwordSecret.key Key in Secret
port RCON port 25575
spec:
  rcon:
    enabled: true
    passwordSecret:
      name: my-server-rcon
      key: password
    port: 25575

Create the secret:

kubectl create secret generic my-server-rcon \
  --from-literal=password=your-secure-password

service

Optional — Configures the Kubernetes Service for the server.

Field Description Default
type Service type LoadBalancer
annotations Custom annotations
loadBalancerIP Static IP for LoadBalancer
spec:
  service:
    type: LoadBalancer
    annotations:
      metallb.universe.tf/loadBalancerIPs: "192.168.1.100"
    loadBalancerIP: "192.168.1.100"

network

Optional — Configures a Kubernetes NetworkPolicy for the server.

Field Description Default
networkPolicy.enabled Create a NetworkPolicy for this server
networkPolicy.allowFrom Additional ingress sources for the Minecraft port
networkPolicy.restrictEgress Restrict outbound traffic to DNS and HTTPS only true
networkPolicy.allowEgressTo Additional egress destinations when restrictEgress is true
spec:
  network:
    networkPolicy:
      enabled: true
      allowFrom:
        - cidr: "10.0.0.0/8"
        - podSelector:
            matchLabels:
              role: proxy
      restrictEgress: true
      allowEgressTo:
        - cidr: "203.0.113.0/24"
          port: 8080
          protocol: TCP

When enabled, the NetworkPolicy allows ingress on the Minecraft game port (25565) and RCON port (if configured), plus any sources listed in allowFrom. Egress is restricted to DNS (port 53) and HTTPS (port 443) by default; use allowEgressTo for additional destinations. Matched Plugin ports are automatically added as ingress rules.

gateway

Optional — Configures Gateway API TCPRoute/UDPRoute for game traffic routing. Requires Gateway API CRDs (experimental channel) installed in the cluster.

Field Description Default
enabled Create Gateway API routes for this server
parentRefs Gateway(s) that routes should attach to
tcpRoute.enabled Create a TCPRoute for game traffic
udpRoute.enabled Create a UDPRoute for game traffic
httpRoutes HTTPRoute configurations for plugin HTTP endpoints
httpRoutes[].pluginName Name of the Plugin resource (same namespace)
httpRoutes[].endpointName Name of the HTTP endpoint in the plugin
httpRoutes[].hostname FQDN for this HTTPRoute
httpRoutes[].pathPrefix Optional path prefix (e.g., /map)
spec:
  gateway:
    enabled: true
    parentRefs:
      - name: minecraft-gateway
        namespace: gateway-system
        sectionName: minecraft-tcp
    tcpRoute:
      enabled: true
    udpRoute:
      enabled: true
    # HTTPRoutes for plugin web interfaces
    httpRoutes:
      - pluginName: bluemap
        endpointName: web-ui
        hostname: map.minecraft.example.com
      - pluginName: plan
        endpointName: web-ui
        hostname: analytics.minecraft.example.com
        pathPrefix: /plan

The operator creates TCPRoute and/or UDPRoute resources that route the Minecraft game port (25565) through the specified Gateway. HTTPRoute resources are created for each httpRoutes entry that references a matched Plugin with an HTTP-protocol endpoint. If the Gateway API CRDs are not installed in the cluster, the operator gracefully skips route management (logs a debug message, no error). When Gateway API CRDs are installed, external modifications to the routes are automatically detected and corrected via owner-reference watches.

backup

Optional — Configures VolumeSnapshot-based backups with RCON consistency hooks.

Field Description Default
enabled Enable backups
schedule Cron schedule for periodic backups
beforeUpdate Create backup before any server update true
volumeSnapshotClassName VolumeSnapshotClass to use cluster default
retention.maxCount Maximum snapshots to retain per server 10
spec:
  backup:
    enabled: true
    schedule: "0 */6 * * *"        # Every 6 hours
    beforeUpdate: true              # Backup before updates
    volumeSnapshotClassName: csi-hostpath-snapclass
    retention:
      maxCount: 10

When RCON is enabled, the operator uses RCON hooks (save-all, save-off, save-on) to ensure world data consistency before creating the VolumeSnapshot. Without RCON, snapshots are crash-consistent only — recent unsaved data may be lost.

Manual trigger:

kubectl annotate papermcserver my-server \
  mc.k8s.lex.la/backup-now="$(date +%s)" \
  --namespace minecraft

pluginConfigs

Optional — Overrides plugin config files for this specific server. When a file path matches a plugin's default config, the server version wins (whole-file replacement).

Field Description
pluginName Name of the Plugin CRD (same namespace)
configs[].configMapRef.name ConfigMap name
configs[].configMapRef.key Key within the ConfigMap
configs[].path Target path relative to plugin directory
configs[].overwrite always (default) or ifNotExists
spec:
  pluginConfigs:
    - pluginName: bluemap
      configs:
        - configMapRef:
            name: prod-bluemap
            key: core.conf
          path: core.conf
          overwrite: always

Merge Priority

For a given plugin file path, the resolution order is:

  1. Server override (pluginConfigs[pluginName].configs[path]) — if present, used
  2. Plugin default (Plugin.spec.configs[path]) — fallback
  3. No config — file not managed by the operator

There is no deep merge. The entire file is replaced.

serverConfigs

Optional — Server-level config files placed relative to the workdir (/data). Use this for server.properties, paper-global.yml, bukkit.yml, and similar files.

Field Description
configMapRef.name ConfigMap name (same namespace)
configMapRef.key Key within the ConfigMap
path Target path relative to /data
overwrite always (default) or ifNotExists
spec:
  serverConfigs:
    - configMapRef:
        name: mc-server-config
        key: server.properties
      path: server.properties
      overwrite: always
    - configMapRef:
        name: mc-server-config
        key: paper-global.yml
      path: config/paper-global.yml
      overwrite: ifNotExists

Implementation Detail

Config files are injected via a config-injector init container that runs before the PaperMC container. The init container uses busybox:1.37 and executes a generated shell script that copies files from ConfigMap volumes to the data PVC.

The script ConfigMap ({server-name}-config-script) is managed by the operator and automatically updated when configs change.

podTemplate

Required — Template for the StatefulSet pod.

This is a standard Kubernetes PodTemplateSpec. Key fields:

Field Description
spec.containers[0].resources CPU/memory requests and limits
spec.containers[0].env Environment variables
spec.volumes Additional volumes
spec:
  podTemplate:
    spec:
      containers:
        - name: minecraft
          resources:
            requests:
              memory: "2Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
          env:
            - name: JAVA_OPTS
              value: "-Xmx3G -Xms1G"
      volumes:
        - name: config
          configMap:
            name: server-config

Status Fields

The operator updates the status to reflect the current state.

currentVersion / currentBuild

Currently running Paper version and build.

status:
  currentVersion: "1.21.4"
  currentBuild: 125

desiredVersion / desiredBuild

Target version the operator wants to run (resolved from updateStrategy).

status:
  desiredVersion: "1.21.4"
  desiredBuild: 130

plugins

List of matched Plugin resources and their versions.

status:
  plugins:
    - pluginRef:
        name: essentialsx
        namespace: minecraft
      resolvedVersion: "2.21.0"
      currentVersion: "2.20.1"
      desiredVersion: "2.21.0"
      compatible: true
      source: hangar

availableUpdate

Next available update if any.

status:
  availableUpdate:
    version: "1.21.4"
    build: 130
    releasedAt: "2024-01-15T10:00:00Z"
    foundAt: "2024-01-16T03:00:00Z"
    plugins:
      - pluginRef:
          name: essentialsx
          namespace: minecraft
        version: "2.21.0"

lastUpdate

Record of the most recent update attempt.

status:
  lastUpdate:
    appliedAt: "2024-01-14T04:00:00Z"
    previousVersion: "1.21.3"
    successful: true

updateBlocked

Indicates if updates are blocked due to compatibility issues.

status:
  updateBlocked:
    blocked: true
    reason: "Plugin incompatible with target version"
    blockedBy:
      plugin: "old-plugin"
      version: "1.0.0"
      supportedVersions:
        - "1.20.4"
        - "1.20.6"

backup (status)

Observed backup state for the server.

status:
  backup:
    backupCount: 5
    lastBackup:
      snapshotName: "survival-backup-1708742400"
      startedAt: "2026-02-24T00:00:00Z"
      completedAt: "2026-02-24T00:00:05Z"
      successful: true
      trigger: "scheduled"
Field Description
backupCount Current number of retained VolumeSnapshots
lastBackup.snapshotName Name of the VolumeSnapshot resource (empty when backup failed before snapshot creation)
lastBackup.startedAt When the backup process started
lastBackup.completedAt When the backup completed
lastBackup.successful Whether the backup succeeded
lastBackup.trigger What triggered the backup (scheduled, before-update, manual)

conditions

Standard Kubernetes conditions.

Type Description
Ready Server reconciled successfully
StatefulSetReady StatefulSet has ready replicas
UpdateAvailable New Paper version/build available
UpdateBlocked Update blocked by plugin incompatibility
Updating Update currently in progress
SolverRunning Constraint solver is executing
CronScheduleValid Maintenance window cron is valid
BackupCronValid Backup cron schedule is valid
BackupReady VolumeSnapshot API is available for backups
ConfigInjectionReady All referenced ConfigMaps for config injection are available

Complete Example

apiVersion: mc.k8s.lex.la/v1beta1
kind: PaperMCServer
metadata:
  name: survival
  namespace: minecraft
  labels:
    environment: production
    server-type: survival
spec:
  updateStrategy: "auto"
  updateDelay: "168h"  # Wait 7 days

  updateSchedule:
    checkCron: "0 3 * * *"
    maintenanceWindow:
      enabled: true
      cron: "0 4 * * 0"

  gracefulShutdown:
    timeout: 300s

  backup:
    enabled: true
    schedule: "0 */6 * * *"
    beforeUpdate: true
    volumeSnapshotClassName: "csi-hostpath-snapclass"
    retention:
      maxCount: 10

  rcon:
    enabled: true
    passwordSecret:
      name: survival-rcon
      key: password

  network:
    networkPolicy:
      enabled: true
      restrictEgress: true

  gateway:
    enabled: true
    parentRefs:
      - name: minecraft-gateway
        namespace: gateway-system
    tcpRoute:
      enabled: true

  service:
    type: LoadBalancer
    annotations:
      external-dns.alpha.kubernetes.io/hostname: survival.minecraft.example.com

  podTemplate:
    spec:
      containers:
        - name: minecraft
          resources:
            requests:
              memory: "4Gi"
              cpu: "1"
            limits:
              memory: "8Gi"
          env:
            - name: JAVA_OPTS
              value: "-Xmx6G -Xms2G -XX:+UseG1GC"
      nodeSelector:
        minecraft: "true"
      tolerations:
        - key: "minecraft"
          operator: "Exists"
          effect: "NoSchedule"

See Also