Backup and Restore Gitea server
Backup and restore Gitea, the right way
Gitea backups touch three moving parts - db, git repos, and app files - and since 2024 the built-in gitea dump command handles all of them in one go.

In the Testing Gitea post we installed the gitea server. This is the follow-up: how to actually back it up and, more importantly, restore it when something goes wrong.
For the complete developer tools collection including Git workflows and Docker management, see Developer Tools: The Complete Guide to Modern Development Workflows.
If you’re setting up Gitea for the first time, check out Choosing free on-prem git server - Gitea is the winner! for installation details, and Gitea SSL with Apache as reverse proxy for secure deployment.
When to rehearse this
Now, just as a precaution against terrible things happening, is a good time to rehearse the backup and restore procedure - before you actually need it, not after a disk dies.
Better be safe than sorry.
Where the data lives
A Gitea instance is really three components that all need to stay in sync:
- code (git repositories)
- filestore (attachments, avatars, LFS objects, indexers)
- db (users, issues, PRs, settings)
In our test environment all together they take a bit more than 700MB:

Because these three pieces reference each other, Gitea’s own docs are explicit about backup consistency: the instance should be stopped for the duration of the backup, otherwise a repo copied mid-migration can end up out of sync with what the database thinks happened. Restore, for the same reason, needs to happen as one transaction too.
The easy way - gitea dump
This is the part that changed since the previous version of this article: instead of juggling pg_dump, tar, and separate folders by hand, Gitea ships a single dump command that bundles the database, repositories, LFS data, attachments, and config into one archive.
gitea dump -c /path/to/app.ini
That produces a timestamped file like gitea-dump-1610949662.zip containing:
app.ini- copy of the config filecustom/- customizations undercustom/data/- attachments, avatars, LFS objects, indexers (and the SQLite file, if that’s your db)repos/- full copy of the repository directorygitea-db.sql- SQL dump of the databaselog/- logs (not needed for restore)
Useful flags worth knowing about:
--file name/-f name- output file name (-for stdout, handy for piping straight toscpor object storage)--type- output format:zip(default),tar,tar.gz,tar.xz,tar.zst, etc.--database/-d- force the SQL dialect ingitea-db.sql(sqlite3,mysql,mssql,postgres) - useful when migrating between database engines--skip-repository/-R,--skip-lfs-data,--skip-attachment-data,--skip-package-data,--skip-log,--skip-db- trim the dump down to just what you need--tempdir/-t- where the intermediate files are staged (default/tmpor$TMPDIR) - make sure this has enough free space for the whole instance
Running gitea dump in Docker
Since most self-hosted setups run Gitea via docker-compose, the command needs to run inside the container, as the git user, from the container’s temp dir:
cd ~/gitea-srv-local
# create a dump inside the running container
sudo docker exec -u git -it -w /tmp gitea bash -c \
'/usr/local/bin/gitea dump -c /data/gitea/conf/app.ini'
# copy the resulting zip out of the container
sudo docker cp gitea:/tmp/gitea-dump-*.zip ./gitea-backups/
-w /tmp matters: dump needs to write and zip its temp working directory, and running it anywhere without write permissions will fail with a permission error.
Then, as before, get the archive off the box:
scp uname@gitea-srv-ip-addr:~/gitea-srv-local/gitea-backups/gitea-dump-*.zip ~/gitea-backups/
If you’d rather dump the database with native tools (Gitea’s own docs recommend this for MySQL/PostgreSQL, since the XORM-based SQL dump inside gitea dump has known edge cases on restore), you can still do it side by side:
sudo docker exec -t gitea-srv-local_db_1 bash -c \
'pg_dump gitea -U gitea --file=/var/lib/postgresql/backups/gitea-db-$(date +%Y-%m-%d).sql'
See the PostgreSQL Cheatsheet for more pg_dump/pg_restore options, and the Docker Compose Cheatsheet if any of the docker exec/docker cp syntax above looks unfamiliar.
How - Restore
There’s still no one-command restore - Gitea’s docs are upfront that this remains a manual process of moving files back into place and restoring the database dump. But! Always check the original doco first, since paths and container layouts change between releases.
# install/start a fresh gitea (same version as the backup) then take it down
sudo docker-compose down
# unzip the dump
unzip gitea-dump-1610949662.zip -d gitea-restore
cd gitea-restore
# restore repos and data into the volumes gitea uses
sudo cp -r repos/* ../gitea/git/
sudo cp -r data/* ../gitea/gitea/
sudo chown -R 1000:1000 ../gitea/git ../gitea/gitea
# bring gitea back up
sudo docker-compose up -d
# restore the database
sudo docker exec -i gitea-srv-local_db_1 psql -U gitea gitea < gitea-db.sql
If you dumped the db separately with pg_dump/pg_restore instead, restore that dump the same way you would for any other Postgres instance - see the PostgreSQL Cheatsheet for the exact commands.
Regenerate hooks after restore
If you restored to a different installation method (binary vs Docker) or a different path, the git hooks baked into each repository will still point at the old paths. Fix that with:
sudo docker exec -u git -it gitea bash -c '/usr/local/bin/gitea admin regenerate hooks'
Skipping this step is the classic cause of push failing right after a restore with no obvious error in the UI.
Verify the restore
Before calling it done:
- Log in to the UI and confirm users, issues, and settings look right.
- Clone one repo and push a trivial commit to confirm hooks work.
- Run
gitea doctor check(add--fixif it flags anything) to catch path or permission mismatches early.
Restoring a single repository - restore-repo
The other genuinely new piece since the last version of this article is a pair of commands for repository-level backup and restore, separate from the full-instance dump/restore workflow above. They’re handy when you only need to recover one project - say, someone force-deleted a repo - instead of rolling back the whole server.
dump-repo pulls a single repository (plus, optionally, its issues, PRs, wiki, and other metadata) out to a local directory:
gitea dump-repo \
--git_service gitea \
--clone_addr https://gitea.example.com/owner/repo.git \
--auth_token <token> \
--repo_dir /backup/repos/owner/repo \
--units wiki,issues,labels,releases,milestones,pull_requests,comments
restore-repo then replays that directory back into an instance, into a chosen owner/repo:
gitea restore-repo \
--repo_dir /backup/repos/owner/repo \
--owner_name owner \
--repo_name repo \
--units issues,labels,milestones,pull_requests,comments
Both --units lists are optional - omit them and everything supported gets migrated. This pair is really a migration tool at heart (it also works against GitHub/GitLab as the source via --git_service), but it doubles nicely as a lightweight, per-repo alternative to a full gitea dump when a full restore would be overkill.
Automating it
A daily dump plus off-box copy is a two-line cron job once the manual steps above work:
# /etc/cron.d/gitea-backup
0 2 * * * root docker exec -u git -w /tmp gitea gitea dump -c /data/gitea/conf/app.ini -f - > /home/uname/gitea-backups/gitea-dump-$(date +\%F).zip 2>> /var/log/gitea-backup.log
Pair it with a retention cleanup (find ~/gitea-backups -mtime +14 -delete) and an off-site copy via scp/rsync so a single-host failure doesn’t take the backups with it.
Troubleshooting
permission deniedduringdump- you’re not running as thegituser, or--tempdir/-wpoints at a directory the container user can’t write to.- Restore succeeds but
git pushfails - hooks weren’t regenerated; rungitea admin regenerate hooks. - Database restore errors on a big instance - prefer native
pg_dump/mysqldumpover the SQL embedded ingitea dump’s zip; it’s known to have rough edges on restore for large or unusual schemas. - Restored to a new major version - run
gitea doctor check --all --fixand check the release notes for that version’s migration steps before assuming the restore is complete.
For quick reference on Git commands, see GIT Cheatsheet: Most useful GIT commands.