Skip to main content

Overview

Oyasai Server Platform implements automated backups using Restic for Minecraft world data and custom backup solutions for MariaDB databases. All backups are stored in Cloudflare R2 object storage.

Minecraft World Backups

The production environment uses the itzg/mc-backup container for automated world backups:
packages/cdktf/src/stacks/docker-stack.ts
new Container(this, this.envAwareId("minecraft-backup-container"), {
  name: "minecraft-main-backup",
  dependsOn: [minecraftMainContainer],
  image: images.minecraftBackup.imageId,
  networksAdvanced: [network],
  restart: "unless-stopped",
  env: envs({
    BACKUP_METHOD: "restic",
    RESTIC_PASSWORD: this.secrets.RESTIC_PASSWORD,
    AWS_ACCESS_KEY_ID: this.secrets.CLOUDFLARE_ACCESS_KEY_ID,
    AWS_SECRET_ACCESS_KEY: this.secrets.CLOUDFLARE_SECRET_ACCESS_KEY,
    RESTIC_VERBOSE: true,
    RESTIC_REPOSITORY: `s3:https://${this.secrets.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/production/minecraft-main-backup`,
    RCON_HOST: "minecraft-main",
    RCON_PASSWORD: this.secrets.RCON_PASSWORD,
    EXCLUDES: "*.jar,cache,logs,*.tmp,bluemap",
    BACKUP_INTERVAL: "6h",
    PRUNE_RESTIC_RETENTION: "--keep-daily 7 --keep-weekly 4 --keep-monthly 3",
  }),
  volumes: [
    {
      hostPath: join(this.workdir, "minecraft-main"),
      containerPath: "/data",
      readOnly: true,
    },
  ],
});

Backup Configuration

BACKUP_METHOD
string
default:"restic"
Backup tool to use (restic recommended for deduplication)
BACKUP_INTERVAL
string
default:"6h"
Frequency of automatic backups (e.g., “6h”, “12h”, “24h”)
RESTIC_REPOSITORY
string
required
S3-compatible storage URL for backup destination
RESTIC_PASSWORD
string
required
Password for encrypting backup repository
RCON_HOST
string
required
Hostname of Minecraft server for save-all command
RCON_PASSWORD
string
required
RCON password for server communication
EXCLUDES
string
Comma-separated patterns for files to exclude from backups

Retention Policy

Backups are automatically pruned according to retention rules:
PRUNE_RESTIC_RETENTION: "--keep-daily 7 --keep-weekly 4 --keep-monthly 3"
Daily Backups
number
Keep the last 7 daily backups
Weekly Backups
number
Keep the last 4 weekly backups
Monthly Backups
number
Keep the last 3 monthly backups

Excluded Files

The following files are excluded from backups to reduce size:
  • *.jar - Plugin JAR files (can be rebuilt)
  • cache - Temporary cache directories
  • logs - Server log files
  • *.tmp - Temporary files
  • bluemap - BlueMap rendered tiles (regenerated)
Excluding regeneratable data significantly reduces backup size and transfer time.

Database Backups

MariaDB databases are backed up using databack/mysql-backup:
packages/cdktf/src/stacks/docker-stack.ts
new Container(this, this.envAwareId("mariadb-backup-container"), {
  name: "mariadb-backup",
  dependsOn: [mariadbContainer],
  image: "databack/mysql-backup",
  restart: "unless-stopped",
  networksAdvanced: [network],
  command: ["dump"],
  env: envs({
    DB_SERVER: "mariadb",
    DB_USER: "root",
    DB_PASS: this.secrets.MARIADB_PASSWORD,
    DB_DUMP_FREQUENCY: 360,
    DB_DUMP_TARGET: `s3://${this.secrets.R2_BUCKET_NAME}/mariadb-backup`,
    AWS_ACCESS_KEY_ID: this.secrets.CLOUDFLARE_ACCESS_KEY_ID,
    AWS_SECRET_ACCESS_KEY: this.secrets.CLOUDFLARE_SECRET_ACCESS_KEY,
    AWS_REGION: "auto",
    AWS_ENDPOINT_URL: `https://${this.secrets.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
    DB_DUMP_COMPRESSION: "gzip",
    DB_DUMP_RETENTION: "14d",
    DB_DEBUG: true,
  }),
});

Database Backup Configuration

DB_SERVER
string
required
Database server hostname (container name on Docker network)
DB_USER
string
required
Database user with backup privileges
DB_PASS
string
required
Database user password
DB_DUMP_FREQUENCY
number
default:"360"
Backup frequency in minutes (360 = 6 hours)
DB_DUMP_TARGET
string
required
S3 storage path for database backups
DB_DUMP_COMPRESSION
string
default:"gzip"
Compression method for SQL dumps
DB_DUMP_RETENTION
string
default:"14d"
How long to retain old backups (e.g., “14d”, “30d”)

Backup Storage

All backups are stored in Cloudflare R2 object storage:

Storage Configuration

RESTIC_REPOSITORY: `s3:https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/production/minecraft-main-backup`

R2 Credentials

Authentication uses AWS S3-compatible credentials:
AWS_ACCESS_KEY_ID
string
required
Cloudflare R2 access key ID
AWS_SECRET_ACCESS_KEY
string
required
Cloudflare R2 secret access key
AWS_REGION
string
default:"auto"
Region setting (use “auto” for R2)

Backup Image Configuration

The mc-backup Docker image is defined in Nix:
packages/mc-backup.nix
{
  oyasaiDockerTools,
  dockerTools,
  stdenv,
}:
let
  inherit (stdenv.hostPlatform) system;
  name = "mc-backup";
  imageName = "itzg/mc-backup";
  hashes = {
    "x86_64-linux" = "sha256-3g2ayNkXxJjaXuC8t7EMDTI9nMH2did75gB/nmNH2Aw=";
    "aarch64-linux" = "sha256-mzKgbyhuH0pMmzFuNKC/ltsDLzx0kfypTEY7XbVPPes=";
  };
in
oyasaiDockerTools.buildImage {
  inherit name;
  fromImage = dockerTools.pullImage {
    inherit imageName;
    imageDigest = "sha256:81e68ecbf7c3452079d08fc7058208cdf493633b4e7431d79d56bdb910c4dfea";
    hash = hashes.${system};
    finalImageName = imageName;
    finalImageTag = "latest";
  };
  config.Entrypoint = [ "/usr/bin/backup" ];
  config.Cmd = [ "loop" ];
  platforms = builtins.attrNames hashes;
}
Supported platforms:
  • x86_64-linux (Intel/AMD)
  • aarch64-linux (ARM64)

Backup Process

The automated backup workflow:
1

Trigger backup

Timer reaches configured interval (6 hours by default)
2

Issue save-all

Backup container connects via RCON and issues save-all command
3

Wait for save

Server completes world save to disk
4

Create snapshot

Restic creates deduplicated snapshot of changed files
5

Upload to R2

Encrypted backup data uploads to Cloudflare R2
6

Prune old backups

Apply retention policy to remove expired backups

Manual Backup Operations

Trigger Manual Backup

# Execute backup immediately
docker exec minecraft-main-backup backup now

# View backup status
docker logs minecraft-main-backup --tail 50

List Backups

# List all snapshots
docker exec minecraft-main-backup restic snapshots

# Show backup statistics
docker exec minecraft-main-backup restic stats

Restore from Backup

Always stop the Minecraft server before restoring to prevent data corruption.
# Stop server
docker stop minecraft-main

# List available snapshots
docker exec minecraft-main-backup restic snapshots

# Restore latest snapshot
docker exec minecraft-main-backup restic restore latest --target /data

# Restore specific snapshot
docker exec minecraft-main-backup restic restore <snapshot-id> --target /data

# Start server
docker start minecraft-main

Database Restore

# Stop database-dependent services
docker stop minecraft-main

# Download backup from R2
aws s3 cp s3://${R2_BUCKET_NAME}/mariadb-backup/backup-YYYY-MM-DD.sql.gz . \
  --endpoint-url https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com

# Restore to database
gunzip < backup-YYYY-MM-DD.sql.gz | docker exec -i mariadb mysql -p

# Restart services
docker start minecraft-main

Backup Monitoring

Check Backup Status

# View backup container logs
docker logs minecraft-main-backup -f

# Check last backup time
docker exec minecraft-main-backup restic snapshots --latest 1

# Verify backup integrity
docker exec minecraft-main-backup restic check

Backup Metrics

# Check repository size and file count
docker exec minecraft-main-backup restic stats

Volume Configuration

Backup containers mount server data as read-only:
volumes: [
  {
    hostPath: join(this.workdir, "minecraft-main"),
    containerPath: "/data",
    readOnly: true,  // Prevent accidental modifications
  },
]
This ensures:
  • No risk of backup process corrupting live data
  • Clear separation between read and write operations
  • Safety during concurrent server operation

Environment-Specific Backups

  • Minecraft: Every 6 hours, retained 7/4/3 (daily/weekly/monthly)
  • Database: Every 6 hours, retained 14 days
  • Storage: Cloudflare R2 production bucket

Backup Best Practices

Test Restores

Regularly test backup restoration to verify integrity and procedures

Monitor Storage

Track R2 storage usage to manage costs and capacity

Verify Encryption

Ensure RESTIC_PASSWORD is securely stored and backed up separately

Document Recovery

Maintain updated disaster recovery documentation

Disaster Recovery

Full Server Restore

1

Provision new server

Deploy fresh Oyasai Server Platform infrastructure
2

Configure R2 access

Set up Cloudflare R2 credentials for backup access
3

Restore world data

Use Restic to restore latest Minecraft world snapshot
4

Restore database

Import latest MariaDB dump from R2 storage
5

Verify integrity

Start server and verify world and player data
6

Resume backups

Ensure backup containers are running and scheduled

Troubleshooting

Verify R2 credentials are correct:
docker exec minecraft-main-backup env | grep AWS
Test R2 connectivity:
docker exec minecraft-main-backup restic snapshots
Check for RCON connectivity:
docker exec minecraft-main-backup nc -zv minecraft-main 25575
Verify RCON password:
docker logs minecraft-main-backup | grep -i rcon
Review exclusion patterns:
docker inspect minecraft-main-backup | jq '.[0].Config.Env' | grep EXCLUDES
Adjust retention policy:
# Reduce retention in docker-stack.ts
PRUNE_RESTIC_RETENTION: "--keep-daily 3 --keep-weekly 2 --keep-monthly 1"

Next Steps

Monitoring

Set up monitoring for backup health and status

Server Configuration

Review server configuration for backup optimization

Build docs developers (and LLMs) love