Self-hosted service
itzg/docker-mc-backup avatar
itzg/docker-mc-backup

docker-mc-backup: a sidecar container that backs up a Minecraft server safely

Provides a side-car container to backup itzg/minecraft-server server data

552 stars69 forksShellMIT

At a glance

What is it?
The itzg/mc-backup image is a sidecar container for the itzg/minecraft-server image that coordinates backups over RCON, asking the server to flush and pause writes before copying the world and resuming afterwards. It offers tar, rsync and restic backends, cron scheduling, pruning policies and a long list of environment variables for every edge case.
Who is it for?
Adopt itzg/mc-backup if you run the itzg/minecraft-server image and want backups that do not race with the server writing its world data, because the RCON coordination is the whole point and a naive file copy will eventually capture a half-written region. Do not adopt it for Bedrock edition, which the readme says is not supported and points at a community solution for.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem a naive backup has

The itzg/mc-backup image provides a side-car container to back up itzg/minecraft-server server data, and the sentence after that is the reason it exists: backups are coordinated automatically by using RCON to flush data, pause writes, and resume after backup is completed.

A Minecraft server writes its world data continuously. Region files are updated as players move, chunks are saved as they are explored, and the server keeps its own state in memory alongside what is on disk. Copying that directory while the server is running gives you a backup that is very probably fine and occasionally not, because you copied a file that was mid-write.

That is the kind of bug that does not show up in testing. It shows up as a restore that fails to load a chunk, or a world that loads but is missing the last few minutes of changes, or a backup that is subtly corrupt and only discovered when you actually need it.

The RCON approach solves it at the protocol level. RCON is the remote console interface Minecraft servers expose, and the server understands save-all, which asks it to write its state to disk, and stop, which shuts it down cleanly. The container uses that to make the world quiescent, copies it, and then puts the server back. The copy is then of a consistent world rather than a lucky one.

The trade is that the backup depends on the server being reachable and on RCON being enabled, which is why RCON_HOST, RCON_PORT, RCON_PASSWORD, RCON_RETRIES and RCON_RETRY_INTERVAL are all configurable, and why there is a separate SERVER_HOST and SERVER_PORT so the game traffic and the RCON traffic can go to different addresses.

The readme is also explicit about scope. This does NOT support Bedrock edition, and it points at a community provided solution for that case instead. Being clear about the boundary is better than a silent partial failure, because a Bedrock server would simply never complete a coordinated backup.

The image is MIT licensed, published as itzg/mc-backup on Docker Hub, and the primary language of the repository is Shell, which is what a sidecar built around shell scripts and a handful of downloaded binaries would be.

The environment variables that matter

The variable list is long, and reading it as a set of decisions rather than a list of knobs gives a good picture of how the tool thinks.

The core four are SRC_DIR, which is /data and points at the server's data directory, BACKUP_NAME, which is world and names the backup, BACKUP_METHOD, which is tar, and BACKUP_INTERVAL, which is 24h. Alongside them, INITIAL_DELAY of 2m delays the first backup after startup, which exists to give the server time to finish starting before the first backup runs, and BACKUP_ON_STARTUP, set to true, controls whether that first backup happens at all.

The timing values accept the sleep format, NUMBER with an optional suffix, and the readme gives the examples. BACKUP_INTERVAL of 1.5d means every one and a half days, 36 hours, and 2h 30m means every two and a half hours. INITIAL_DELAY of 120 means two minutes. The suffixes are s for seconds, which is the default, m for minutes, h for hours and d for days.

The interval mechanism has an important edge case. If BACKUP_INTERVAL is set to 0 or smaller, the script runs once and exits, which is the mode you want for a one-shot backup in a CI job or a cron-driven script.

The excludes and includes are glob(3) patterns. INCLUDES defaults to a single dot, meaning the whole directory, and it is a comma separated list of include patterns relative to SRC_DIR. EXCLUDES defaults to *.jar,cache,logs,*.tmp, which excludes plugin and server jar files, the logs folder and the cache used by servers such as PaperMC. There is also an EXCLUDES_FILE option to read the list from a file, one pattern per line, which can be combined with EXCLUDES to add to it.

Then there are the three switches that control the coordination itself, and these are the ones to understand before changing anything. ENABLE_SAVE_ALL defaults to true and controls whether the save-all server command runs before the backup, and the readme tells you to set it false only if the server hangs on save-all and you are sure autosave is keeping the world current. ENABLE_SYNC defaults to true and controls the sync Linux command that flushes the file system buffers to disk after save-all, and the readme points at an issue explaining when to disable it, for example in cluster environments.

The logic of that pairing is worth stating plainly. save-all asks the server to write its world. sync asks the kernel to write its buffers. Both are needed for a copy taken immediately afterwards to be complete, and both can be turned off independently when they cause problems.

Backup methods and pruning

Three backup methods are offered, and the choice between them is about where the data goes and how much you keep.

The tar method is the default. DEST_DIR is /backups, LINK_LATEST defaults to false and creates a symbolic link to the latest backup when enabled, TAR_COMPRESS_METHOD defaults to gzip, and TAR_COMPRESS_PARAMETERS sets the compression parameters, with leaving it blank being fine. The valid compression methods are bzip2, gzip, lzip, lzma, lzop, xz and zstd.

The rsync method also uses /backups and offers the same LINK_LATEST flag. The difference is that rsync transfers rather than archives, which is the right choice when the destination is reachable and you want incremental transfers rather than a full copy each time.

The restic method is the one with different semantics. RESTIC_ADDITIONAL_TAGS defaults to mc_backups and applies extra tags to the backup, and setting it to an empty string disables the additional tags. RESTIC_VERBOSE toggles verbose output. Restic is a deduplicating, content-addressed backup tool, so the archive is incremental by design, and pruning works differently because restic has its own retention model.

That difference shows up in the pruning variables. PRUNE_BACKUPS_DAYS defaults to 7 and deletes old .tgz backup files from DEST_DIR, so by default the container keeps a week of tar archives. PRUNE_BACKUPS_COUNT is disabled unless set and only works with tar or rsync. And PRUNE_RESTIC_RETENTION defaults to --keep-within 7d, which is restic's own retention syntax rather than a day count, because restic keeps snapshots according to a policy expression.

The separation is deliberate and correct. A container that deleted restic snapshots by counting days would break restic's deduplication, so the restic retention stays in restic's language and the other two methods get the simple day and count policy.

The lock behaviour completes the picture. SKIP_LOCKING is true for restic and false for the others, and it skips locking the DEST_DIR, which prevents concurrent backup operations such as a scheduled run and a manual now run happening at the same time.

Cron scheduling, and the root warning

Cron scheduling is opt-in, and it comes with a security warning that is worth reading before enabling it.

Set CRON_SCHEDULE to a cron expression to enable it. The examples are every day at 4 am with 0 4 * * *, every hour with 0 * * * *, and at the 1st day of every month with 0 0 1 * *. The schedule is disabled unless you set the variable.

The time is in UTC by default. To use the server's local time you pass the timezone into the container, and the readme shows the compose snippet that does it:

yaml
backup:
  image: itzg/mc-backup
  restart: unless-stopped
  environment:
    CRON_SCHEDULE: "0 4 * * *"
    CRON_BACKUP_UID: "1000"
  volumes:
    /etc/localtime:/etc/localtime:ro
    /etc/timezone:/etc/timezone:ro

Mounting the host's timezone files read-only is the standard container technique for this, and it works because the container reads the same offset data the host does.

The warning is that the container must be run with the root user to launch crond, and while CRON_BACKUP_UID lets you run the schedule as a non-root user, that is still less secure than running the container as non-root in the first place. The CRON_BACKUP_UID variable sets a user id and the backup processes are spawned with that user, but the service attribute user is incompatible with cron scheduling, so you cannot have both.

The note underneath explains the other consequence. Setting CRON_SCHEDULE overrides the other interval based backup triggering, so INITIAL_DELAY, BACKUP_INTERVAL, BACKUP_ON_STARTUP, PAUSE_IF_NO_PLAYERS and PLAYERS_ONLINE_CHECK_INTERVAL have no effect while it is set. Two schedulers would otherwise both try to run backups.

That is a sensible design, but it means the mode has to be chosen rather than combined. A user who turns on cron without realising the interval settings go dead will find their backups running at 4 am and nowhere else, which is probably what they wanted, but it is worth knowing rather than discovering.

The pause-if-no-players behaviour and the hooks

One feature deserves its own explanation because it changes the schedule semantics in a non-obvious way.

PAUSE_IF_NO_PLAYERS defaults to false. When it is true and there are no players online after a backup is made, the script does not immediately schedule the next backup. Instead it starts checking the server's player count every PLAYERS_ONLINE_CHECK_INTERVAL, which defaults to 5 minutes. Once a player joins, the next backup is scheduled in BACKUP_INTERVAL.

So the backup schedule becomes conditional on activity. A server that nobody plays on does not accumulate backups, and the moment somebody logs in the schedule resumes. For a small community server that is often the difference between a disk full of identical backups and a sensible retention.

The hook variables are the other piece of the configuration surface. PRE_SAVE_ALL_SCRIPT, PRE_BACKUP_SCRIPT, PRE_SAVE_ON_SCRIPT and POST_BACKUP_SCRIPT, along with the corresponding *_SCRIPT_FILE variants, let you run your own commands around the phases of the backup cycle. PRE_SAVE_ALL runs before the save-all command, PRE_BACKUP before the backup begins, PRE_SAVE_ON before the server is resumed, and POST_BACKUP after it finishes.

That is four distinct extension points around a coordinated sequence, and each one has a defined position. That is what makes it possible to do something like announce a backup on chat, or notify a monitoring system, or run a custom consistency check, without replacing the whole script.

The remaining variables are smaller but each removes a hardcoded assumption. RCON_PASSWORD_FILE reads the RCON password from a file and overrides RCON_PASSWORD if both are set, which is the right way to keep a secret out of an environment listing. RCON_RETRIES defaults to 5 and can be set to a negative value to retry indefinitely. TZ sets the timezone for logging.

The top level of the repository shows a well-organised project behind this. There is a scripts/ directory holding the shell logic, a tests/ directory, an examples/ directory with compose files for each backup method including restic with rclone, a .hadolint.yaml so the Dockerfile is linted, a renovate.json5 for dependency updates, and a test-deploy.yaml. The example files cover the plain compose setup, rsync, restic, and restic with rclone, which is a good sign that the documented paths are the tested ones.

The releases are dated rather than numbered semantically, 2026.10.1 from 2026-10-16, 2026.10.0 from 2026-10-12 and 2026.9.3 from 2026-09-30, which is a calendar versioning scheme that tells you when a change shipped without implying anything about compatibility.

Editorial conclusion

Adopt itzg/mc-backup if you run the itzg/minecraft-server image and want backups that do not race with the server writing its world data, because the RCON coordination is the whole point and a naive file copy will eventually capture a half-written region. Do not adopt it for Bedrock edition, which the readme says is not supported and points at a community solution for. Before you commit, set the RCON password to something other than the default and use RCON_PASSWORD_FILE rather than the variable, check that your server has RCON enabled on port 25575, and think about PRUNE_BACKUPS_DAYS, since the default of seven is short if your restores take a while to notice a problem.

Frequently asked questions

What does the itzg/mc-backup container do?

It provides a side-car container to back up itzg/minecraft-server server data. Backups are coordinated automatically by using RCON to flush data, pause writes and resume after the backup is completed, so the copy is of a consistent world rather than one taken while the server was writing.

Which backup methods does mc-backup support?

Three, set with BACKUP_METHOD. The default tar method writes compressed archives to DEST_DIR with TAR_COMPRESS_METHOD and optional parameters, rsync transfers to the destination instead, and restic uses its own content-addressed repository with additional tags and a retention policy. Pruning is separate for each: PRUNE_BACKUPS_DAYS and PRUNE_BACKUPS_COUNT for tar and rsync, and PRUNE_RESTIC_RETENTION for restic.

How do I enable cron scheduling in mc-backup?

Set CRON_SCHEDULE to a cron expression, for example 0 4 * * * for a daily backup at 4 am. The schedule is in UTC unless you mount the host's timezone files. The container must run as root to launch crond, and while CRON_BACKUP_UID spawns the backups as a non-root user, that is still less secure than running the container as non-root. Enabling cron overrides the interval based triggering entirely.

What does PAUSE_IF_NO_PLAYERS do?

When it is true and no players are online after a backup, the container does not schedule the next one immediately. It checks the player count every PLAYERS_ONLINE_CHECK_INTERVAL, five minutes by default, and schedules the next backup in BACKUP_INTERVAL once somebody joins the server.

Does mc-backup support Minecraft Bedrock edition?

No. The readme states plainly that it does not support Bedrock edition and points to a community provided solution hosted in the itzg/docker-minecraft-bedrock-server repository for that case. The tool is designed for the Java edition server image.

Official sources

  1. itzg/docker-mc-backup on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/itzg-docker-mc-backup.svg)](https://hysenlabs.com/projects/itzg-docker-mc-backup)

Was this useful?