Restoring a Forgejo backup
Self-hosting is a fun and interesting way to learn about the technologies you use. One thing you have to take care of is to have backups of important systems. I had to upgrade forgejo from v13 to v14, but you want to be sure that works before doing it, right?
How backups are made
On my devbox I have a daily backup that creates an off-site backup of the volumes.
#!/bin/bash
# Variables
BACKUP_DIR=/mnt/backups-devbox
FOLDERS_TO_BACKUP=("forgejo")
if [ ! -d $BACKUP_DIR ]; then
echo "Backup dir ($BACKUP_DIR) does not exist, creating."
mkdir $BACKUP_DIR
fi
if [ ! -e $BACKUP_DIR/.canary ]; then
echo "Canary file not found, not doing backups!"
exit 1
fi
echo "Creating backups of PostgreSQL"
DB_USER=forgejo
CONTAINER_NAME=db
# Get current date and time for backup file
TIMESTAMP=$(date +"%F_%T")
BACKUP_FILE_FORGEJO=$BACKUP_DIR/backup_forgejo_$DB_NAME_$TIMESTAMP.sql
# Run pg_dump inside the PostgreSQL container
docker exec -t $CONTAINER_NAME pg_dump -U $DB_USER forgejo >$BACKUP_FILE_FORGEJO &&
echo "Backup completed: $BACKUP_FILE_FORGEJO"
echo "Creating compressed tarballs of specified folders"
for FOLDER in "${FOLDERS_TO_BACKUP[@]}"; do
if [ -d "$FOLDER" ]; then
# Create a filename-friendly string from the path (e.g., /etc/nginx -> etc_nginx)
FOLDER_NAME=$(echo "$FOLDER" | sed 's/^\///; s/\//_/g')
TAR_NAME="$BACKUP_DIR/folder_${FOLDER_NAME}_$TIMESTAMP.tar.gz"
echo "Backing up $FOLDER to $TAR_NAME..."
tar -czf "$TAR_NAME" "$FOLDER"
else
echo "Warning: Folder $FOLDER not found, skipping."
fi
done
echo "Backups done"
This is of course all very well, but this backup needs to be tested.
An untested backup is no backup.
Running a test setup
I am running Forgejo as a docker container on my devbox. The actual configuration is done with ansible and it sits nicely behind a reverse proxy, but for testing purposes locally this docker-compose file builds the same setup:
services:
db:
image: postgres:16
container_name: db
restart: always
ports:
- "5432:5432"
environment:
USER_UID: "1000"
USER_GID: "1000"
POSTGRES_USER: "forgejo"
POSTGRES_PASSWORD: "forgejo"
POSTGRES_DB: "forgejo"
healthcheck:
test: ["CMD-SHELL", "pg_isready", "-d", "forgejo"]
interval: 30s
timeout: 60s
retries: 5
start_period: 80s
volumes:
- ./postgres:/var/lib/postgresql/data
networks:
- forgejo
forgejo:
image: codeberg.org/forgejo/forgejo:14
container_name: forgejo
restart: always
ports:
- "3000:3000"
- "22:22"
environment:
USER_UID: "1000"
USER_GID: "1000"
FORGEJO__database__DB_TYPE: "postgres"
FORGEJO__database__HOST: "db:5432"
FORGEJO__database__NAME: "forgejo"
FORGEJO__database__USER: "forgejo"
FORGEJO__database__PASSWD: "forgejo"
volumes:
- ./forgejo:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- forgejo
networks:
forgejo:
name: forgejo
Restoring postgres
So, first I only run the postgres container so that I can re-create the database. The restore is most easily done by copying the dump into the image and restoring it:
docker cp backup_forgejo_2026-SOMEDATE.sql db:/tmp/dump.sql
docker exec -it db psql -U forgejo -d forgejo -f /tmp/dump.sql
Restoring forgejo
Then it is time to recreate the forgejo directory from the backup:
tar zxvf folder_forgejo_2026-SOMEDATE.tar.gz
Remember to run these commands in the directory of the docker-compose file.
Finally ensure everything is fine by running the forgejo docter:
docker exec -u git -ti forgejo bash
forgejo doctor check --all