A clone preserves Git repository content but cannot restore the whole Gitea service. User accounts, permissions, issues and Pull Requests depend on the database; attachments, LFS objects, settings and keys may live elsewhere. Plan a consistent backup and test using it in an isolated restore.
We use the named volume-based Compose configuration from Part 15. If you added an external object store, package registry, or separate LFS path, you must include that data as well. Below is an approach that stops writing and achieves a consistent state in a small practice, and is not a non-disruptive backup design.
Git · GitOps All Series · 16 / 24
On this page
What to keep together
| target | What it includes | Impact of falling out |
|---|---|---|
| PostgreSQL dump | Users, permissions, issues, PR, etc. | Service status other than code cannot be recovered |
Gitea /data | Storage, settings, keys, internal attachments/LFS, etc. | Loss of file/encrypted data/service identification information |
Compose and .env | Image/volume/DB connection settings | Difficulty reproducing the same configuration and accessing the DB |
| External storage/connection information | Separate object data and operational settings | Some files or service integrations are missing |
Settings like SECRET_KEY may be required to decrypt encrypted material. Since the backup itself is a bundle of sensitive data, access is restricted, encrypted, and stored in a different failure zone. A copy placed only on the same disk as the original server is not prepared for a complete server failure.
1. Stop writing and back up
Save the example file server/backup.sh to the same directory as Compose and run it. The backup path is specified as a new absolute path. It uses Bash’s set -euo pipefail, so it runs on Linux Bash.
#!/usr/bin/env bash
set -euo pipefail
cd -- "$(dirname -- "$0")"
umask 077
backup_dir="${1:?Usage: ./backup.sh /absolute/new-backup-directory}"
[[ "$backup_dir" = /* ]] || { echo 'Use an absolute path' >&2; exit 1; }
mkdir -m 700 -- "$backup_dir"
docker compose stop gitea
trap 'docker compose start gitea >/dev/null' EXIT
docker compose exec -T db pg_dump -U gitea -d gitea -Fc > "$backup_dir/gitea.dump"
docker compose run --rm --no-deps --user 0 --entrypoint tar gitea -C /data -czf - . > "$backup_dir/gitea-data.tar.gz"
cp compose.yaml .env "$backup_dir/"
sha256sum "$backup_dir/gitea.dump" "$backup_dir/gitea-data.tar.gz" > "$backup_dir/SHA256SUMS"
echo "Backup created: $backup_dir"
chmod 700 backup.sh
./backup.sh "$HOME/gitea-backup-20261018"
The script stops Gitea, dumps the DB, and saves the entire /data as tar. Ephemeral containers read existing Gitea volumes. The reason for reading as root is to preserve file ownership and access scope, and no services are provided by this container.
trap attempts to restart Gitea even if an error occurs. If the backup command fails, it only looks at the file size and does not treat it as a success. After checking the DB dump, tar integrity, and checksum, check the service in status and logs.
docker compose ps
tar -tzf "$HOME/gitea-backup-20261018/gitea-data.tar.gz" | head
2. Restore to a project different from the original
To restore on the same host, specify a different project name, port, and URL. Here, we assume that you copy Compose and .env from the backup to a new directory and change them to name: maruops-git-restore, web port 13001, and SSH port 12223. Also set ROOT_URL and SSH_PORT respectively.
Do not attach the original maruops-git-lab volume as an external volume. Use a new, empty volume created by the separate project. Do not run the following restore commands over existing data.
# Run in the new restore directory after editing its compose.yaml.
backup_dir="$HOME/gitea-backup-20261018"
sha256sum -c "$backup_dir/SHA256SUMS"
docker compose config --quiet
docker compose up -d db
# Wait until DB is healthy, then restore into its fresh gitea database.
docker compose exec -T db pg_restore -U gitea -d gitea --exit-on-error < "$backup_dir/gitea.dump"
docker compose run --rm --no-deps --user 0 --entrypoint tar gitea \
-C /data -xzf - < "$backup_dir/gitea-data.tar.gz"
docker compose up -d gitea
The restore will not start before the DB becomes healthy. After restoring to the same Gitea version and the same PostgreSQL major version as the original, check the status and proceed with the upgrade as a separate step. Upgrading the DB major by only changing the image tag may cause data directory compatibility issues.
3. Restoration success is judged by usage actions.
Log in to your existing account at the restored web address and verify that you can see the repository, issues, and PRs. Clone to a new path and run git log and git fsck --full. If used, LFS file downloads, attachments, and SSH clones are also checked respectively. Just because you see the list doesn’t mean you’ve got all the data back.
The restoration environment can re-execute the original mail, webhook, and mirroring operations. Ensure your test environment does not touch operational automation by limiting external connections or turning off relevant features first. The verification scope of this series is recorded as actual results on the information page.
Refer to Gitea backup/restore document for storage location and recovery precautions, and PostgreSQL pg_restore for DB restore options. When deciding your next backup cycle, write down the data loss time and recovery time you will allow.
Previous · [Git and GitOps 15] Run a Local Gitea Server with Docker Compose · Next · [Git and GitOps 17] Add GitHub Actions CI and Stop on Test Failures · series list and example file



