Back to blog
Case StudyGiteaBackupDockerDevOps

How We Automated Gitea Backups Using Krons24 — A Dog Food Story

We use our own product to backup the Gitea instance that holds our source code. Here's the problem we solved, the approach we took, and the real numbers from production.

A

Aftab Aziz

Software Engineer, Teknomee

April 13, 20266 min read

If your engineering team self-hosts Gitea, your entire organization probably lives inside it. Every line of source code, every pull request, every issue — all of it sits on one instance. Losing that instance would mean losing weeks of work, months of history, and engineering velocity overnight.

When we audited our own backup strategy for the Gitea instance we use internally, we expected the answer to be simple. It wasn't.

The Problem: gitea dump Alone Wasn't Enough

Gitea ships with a built-in dump command that bundles repositories, database, and configuration into a single archive. On paper, perfect. In reality, we ran into a subtle but critical issue: the database dump and the file system copy happen sequentially — not atomically.

Here's what that means in practice. Suppose Gitea starts dumping the database at 02:00:00 and finishes at 02:00:03. Then it starts copying files and finishes at 02:00:15. In that 15-second window, an automated Jenkins build pushes a commit. The database captures the new commit's metadata — but the file system copy happens moments later, possibly with the commit files half-written. On restore, you get a database that references commits that don't fully exist in the filesystem. Silent corruption.

The Silent Failure

Inconsistent backups look fine on the surface. The archive is created, the log says 'success', and you move on. The corruption only surfaces months later when you try to restore — and by then, every backup you took during the broken window is unreliable.

The Requirement: Atomic, Verifiable, Automated

We needed three things, non-negotiable:

  • Atomicity — Database and files must represent the exact same moment in time
  • Integrity — A way to verify the archive wasn't corrupted between backup and restore
  • Automation — No human in the loop, scheduled and monitored, with alerts on failure

The Solution: A 90-Line Shell Script + Krons24

We wrote a backup script that solves the consistency problem by briefly pausing the Gitea Docker container during the backup window. While paused, no reads or writes can happen — so the database dump and file copy capture the exact same state. Total downtime: a few seconds at 2 AM, when nobody is pushing code.

The flow is straightforward:

  • Pre-checks: Docker running, containers healthy, disk space available
  • Clean up local backups older than 3 days (retention policy)
  • Pause the Gitea container — freeze all writes
  • Dump MySQL with mysqldump --single-transaction (extra safety)
  • Copy Gitea data directory (repos, config, attachments, avatars)
  • Unpause the container — back to normal operation
  • Bundle everything into a single .tar.gz archive
  • Generate SHA-256 checksum for integrity verification
  • Log every step with timestamps

Safety Net: Automatic Unpause

The script uses a trap handler so that even if it crashes mid-execution, the Gitea container is guaranteed to be unpaused before exit. Production can never be left frozen.

Why We Scheduled It With Krons24 (Our Own Product)

We could have used plain crontab. But crontab gives you nothing: no execution history, no retry logic, no failure alerts, no log viewer, no way to know if last night's backup actually ran without SSH-ing into the server.

Krons24 gives us all of that out of the box — after all, that's what we built it for. We registered the Gitea instance with Krons24, pointed it at the backup script, set the schedule to run daily at 2:00 AM, and moved on with our day.

  • Every run shows up in the dashboard with status, duration, and exit code
  • Full execution logs are captured and stored — no SSH required to debug
  • Failures trigger alerts immediately — we know within minutes, not days
  • Execution history is queryable — we can prove every backup ran for compliance
Krons24 execution history dashboard showing total runs, success rate, and per-run status
Execution history for the Gitea backup job — every run tracked, with status, duration, and exit code.

When a run fails, we don't need to SSH anywhere. We click into the failed execution and read the captured logs directly from the Krons24 dashboard — the same output the script wrote to stdout, preserved and timestamped.

Krons24 historical execution logs showing timestamped output from a Gitea backup run
Historical execution logs — full script output preserved per run, searchable after the fact.

The Numbers From Production

Here's what a real backup looks like on the Gitea instance we run internally:

Database Dump

18 MB

Gitea Data Files

694 MB

Compressed Archive

592 MB

Container Downtime

15 sec

Total Execution

33 sec

Krons24 Status

✓ Green

33 seconds, end to end. 15 seconds of that is the atomic snapshot window. The rest is compression, checksum generation, and retention cleanup. All of it monitored by Krons24, all of it logged, all of it verifiable.

What This Means for Your Team

If you're running any containerized service in production — Gitea, PostgreSQL, Redis, MongoDB, your own backend — you almost certainly need a backup strategy. And you almost certainly need scheduled jobs to run consistently, with visibility when they fail.

You don't need to build crontab plumbing, log aggregation, retry logic, alert pipelines, and dashboards from scratch. That's what Krons24 is for. We trust it with our own source code backups. You can trust it with yours.

Want the Script?

Our Gitea backup script is under 100 lines of Bash. If you're running Gitea in Docker and want to set up the same atomic backup flow, reach out — we'll share the template and the Krons24 job configuration.

Ready to schedule smarter?

Try Krons24 for your own scheduled jobs. Set up in minutes, trusted in production.