Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d150b7c25e | |||
| 5b172385ca | |||
| 2ab7be67fa | |||
| 5bfbda5e5e | |||
| 89deb6eec9 | |||
| 5698b278b5 |
5 changed files with 122 additions and 12 deletions
|
|
@ -5,6 +5,6 @@ RUN apk add --no-cache restic docker-cli docker-cli-compose curl zstd bash
|
||||||
COPY backup.sh /backup.sh
|
COPY backup.sh /backup.sh
|
||||||
COPY backup_local.sh /backup_local.sh
|
COPY backup_local.sh /backup_local.sh
|
||||||
COPY backup_restic.sh /backup_restic.sh
|
COPY backup_restic.sh /backup_restic.sh
|
||||||
RUN chmod +x /backup.sh
|
RUN chmod +x /backup.sh /backup_local.sh /backup_restic.sh
|
||||||
|
|
||||||
ENTRYPOINT [ "/backup.sh" ]
|
ENTRYPOINT [ "/backup.sh" ]
|
||||||
|
|
|
||||||
83
README.md
83
README.md
|
|
@ -68,10 +68,11 @@ This container automatically detects the database type (MariaDB or PostgreSQL) i
|
||||||
|
|
||||||
### Backup Methods
|
### Backup Methods
|
||||||
|
|
||||||
The container supports two backup methods:
|
The container supports three execution modes:
|
||||||
|
|
||||||
1. **`restic`** - Backup to Restic repositories (cloud storage, remote servers)
|
1. **`restic`** - Backup to Restic repositories (cloud storage, remote servers)
|
||||||
2. **`local`** - Create compressed local backup files
|
2. **`local`** - Create compressed local backup files
|
||||||
|
3. **`loop`** - Keep container running for external schedulers (e.g., Ofelia, Kubernetes CronJob)
|
||||||
|
|
||||||
### Restic Backups
|
### Restic Backups
|
||||||
|
|
||||||
|
|
@ -97,6 +98,18 @@ docker run --rm \
|
||||||
gitea.ceperka.net/rosti/db-backup:latest local
|
gitea.ceperka.net/rosti/db-backup:latest local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Loop Mode (For External Schedulers)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e CONTAINER=my-mariadb-container \
|
||||||
|
-e MARIADB_ROOT_PASSWORD=db-password \
|
||||||
|
gitea.ceperka.net/rosti/db-backup:latest loop
|
||||||
|
```
|
||||||
|
|
||||||
|
In loop mode, the container stays running indefinitely, allowing external schedulers like Ofelia, Kubernetes CronJobs, or other orchestrators to execute the backup scripts directly inside the running container.
|
||||||
|
|
||||||
### With Docker Compose
|
### With Docker Compose
|
||||||
|
|
||||||
#### Restic Backup Setup
|
#### Restic Backup Setup
|
||||||
|
|
@ -163,6 +176,52 @@ volumes:
|
||||||
db_data:
|
db_data:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Loop Mode with External Scheduler (Ofelia)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: mariadb:latest
|
||||||
|
environment:
|
||||||
|
MARIADB_ROOT_PASSWORD: secretpassword
|
||||||
|
MARIADB_DATABASE: myapp
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
backup:
|
||||||
|
image: gitea.ceperka.net/rosti/db-backup:latest
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
environment:
|
||||||
|
CONTAINER: database
|
||||||
|
TARGET_DIR: /backups
|
||||||
|
MARIADB_ROOT_PASSWORD: secretpassword
|
||||||
|
NOTIFY_URL: https://hc-ping.com/your-healthcheck-uuid
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./backups:/backups
|
||||||
|
command: ["loop"]
|
||||||
|
labels:
|
||||||
|
ofelia.enabled: "true"
|
||||||
|
ofelia.job-exec.backup-local.schedule: "0 2 * * *"
|
||||||
|
ofelia.job-exec.backup-local.command: "/backup_local.sh"
|
||||||
|
ofelia.job-exec.backup-restic.schedule: "0 3 * * *"
|
||||||
|
ofelia.job-exec.backup-restic.command: "/backup_restic.sh"
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
image: mcuadros/ofelia:latest
|
||||||
|
depends_on:
|
||||||
|
- backup
|
||||||
|
command: daemon --docker
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
```
|
||||||
|
|
||||||
### Scheduled Backups with Cron
|
### Scheduled Backups with Cron
|
||||||
|
|
||||||
To run backups on a schedule, you can use cron or a container orchestrator:
|
To run backups on a schedule, you can use cron or a container orchestrator:
|
||||||
|
|
@ -179,6 +238,28 @@ To run backups on a schedule, you can use cron or a container orchestrator:
|
||||||
0 3 * * * docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /host/backups:/backups -e CONTAINER=my-db -e TARGET_DIR=/backups -e MARIADB_ROOT_PASSWORD=dbpass gitea.ceperka.net/rosti/db-backup:latest local
|
0 3 * * * docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /host/backups:/backups -e CONTAINER=my-db -e TARGET_DIR=/backups -e MARIADB_ROOT_PASSWORD=dbpass gitea.ceperka.net/rosti/db-backup:latest local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### External Schedulers with Loop Mode
|
||||||
|
|
||||||
|
When using loop mode, you can execute backups from external schedulers by running the backup scripts directly inside the running container:
|
||||||
|
|
||||||
|
#### With Ofelia Scheduler
|
||||||
|
Ofelia can execute jobs in running containers using labels (see Docker Compose example above).
|
||||||
|
|
||||||
|
#### Manual Execution in Loop Mode
|
||||||
|
```bash
|
||||||
|
# Execute local backup in running container
|
||||||
|
docker exec <container-name> /backup_local.sh
|
||||||
|
|
||||||
|
# Execute restic backup in running container
|
||||||
|
docker exec <container-name> /backup_restic.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### With Kubernetes CronJob + Running Pod
|
||||||
|
```bash
|
||||||
|
# Execute backup in running pod
|
||||||
|
kubectl exec <pod-name> -- /backup_local.sh
|
||||||
|
```
|
||||||
|
|
||||||
### Kubernetes CronJob
|
### Kubernetes CronJob
|
||||||
|
|
||||||
#### Restic Backup CronJob
|
#### Restic Backup CronJob
|
||||||
|
|
|
||||||
10
backup.sh
10
backup.sh
|
|
@ -1,9 +1,17 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
if [ "$1" = "local" ]; then
|
if [ "$1" = "local" ]; then
|
||||||
source /backup_local.sh
|
source /backup_local.sh
|
||||||
elif [ "$1" = "restic" ]; then
|
elif [ "$1" = "restic" ]; then
|
||||||
source /backup_restic.sh
|
source /backup_restic.sh
|
||||||
|
elif [ "$1" = "loop" ]; then
|
||||||
|
# Infinite loop to keep the container running and let Ofelia scheduler (or any other) manage the execution
|
||||||
|
echo "Entering infinite loop mode. Use external scheduler to trigger backups."
|
||||||
|
while true; do
|
||||||
|
sleep 86400
|
||||||
|
done
|
||||||
else
|
else
|
||||||
echo "Unknown backup method. Use 'local' or 'restic'."
|
echo "Unknown backup method. Use 'local', 'loop or 'restic'."
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
#
|
#
|
||||||
# CONTAINER - name of the container where the database is running
|
# CONTAINER - name of the container where the database is running
|
||||||
# NOTIFY_URL - optional, URL to send notification to
|
# NOTIFY_URL - optional, URL to send notification to
|
||||||
# TARGET_DIR - directory where to store the backup
|
# TARGET_DIR - directory where to store the backup
|
||||||
|
# HISTORY - number of backups to keep (default: 3)
|
||||||
|
|
||||||
if [ -z "$CONTAINER" ]; then
|
if [ -z "$CONTAINER" ]; then
|
||||||
echo "CONTAINER environment variable is not set."
|
echo "CONTAINER environment variable is not set."
|
||||||
|
|
@ -16,7 +19,12 @@ if [ -z "$TARGET_DIR" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! `docker info` ]; then
|
if [ -z "$HISTORY" ]; then
|
||||||
|
HISTORY=3
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker info > /dev/null 2>&1
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
echo "Docker is not available."
|
echo "Docker is not available."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -30,8 +38,9 @@ fi
|
||||||
echo "Starting backup for container: $CONTAINER"
|
echo "Starting backup for container: $CONTAINER"
|
||||||
if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || echo "no"` = "yes" ]; then
|
if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || echo "no"` = "yes" ]; then
|
||||||
DBTYPE="mariadb"
|
DBTYPE="mariadb"
|
||||||
FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER_$DB_NAME.sql.zst.tmp
|
FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER.sql.zst
|
||||||
docker exec -i $CONTAINER mariadb-dump \
|
docker exec -i $CONTAINER mariadb-dump \
|
||||||
|
--all-databases \
|
||||||
--user=root \
|
--user=root \
|
||||||
--password=$MARIADB_ROOT_PASSWORD \
|
--password=$MARIADB_ROOT_PASSWORD \
|
||||||
--add-drop-trigger \
|
--add-drop-trigger \
|
||||||
|
|
@ -42,12 +51,12 @@ if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || e
|
||||||
--events \
|
--events \
|
||||||
--routines \
|
--routines \
|
||||||
--single-transaction \
|
--single-transaction \
|
||||||
--triggers | zstd -1 > $TARGET_DIR/
|
--triggers | zstd -1 > $TARGET_DIR/$FILENAME.tmp
|
||||||
mv $TARGET_DIR/$DBTYPE_$CONTAINER_$DB_NAME.sql.zst.tmp $TARGET_DIR/$DBTYPE_$CONTAINER_$DB_NAME.sql.zst
|
mv $TARGET_DIR/$FILENAME.tmp $TARGET_DIR/$FILENAME
|
||||||
elif [ `docker exec -i $CONTAINER test -e /usr/bin/pg_dump && echo "yes" || echo "no"` = "yes" ]; then
|
elif [ `docker exec -i $CONTAINER test -e /usr/bin/pg_dump && echo "yes" || echo "no"` = "yes" ]; then
|
||||||
DBTYPE="pgsql"
|
DBTYPE="pgsql"
|
||||||
FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER_$DB_NAME.sql.zst
|
FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER.sql.zst
|
||||||
docker exec -i $CONTAINER pg_dump --username=$DB_USER --password=$PGPASSWORD $DB_NAME | zstd -1 > $TARGET_DIR/$FILENAME.tmp
|
docker exec -i $CONTAINER pg_dumpall --username=$DB_USER --password=$PGPASSWORD | zstd -1 > $TARGET_DIR/$FILENAME.tmp
|
||||||
mv $TARGET_DIR/$FILENAME.tmp $TARGET_DIR/$FILENAME
|
mv $TARGET_DIR/$FILENAME.tmp $TARGET_DIR/$FILENAME
|
||||||
else
|
else
|
||||||
echo "Unsupported database type or database client not found in the container."
|
echo "Unsupported database type or database client not found in the container."
|
||||||
|
|
@ -56,6 +65,14 @@ fi
|
||||||
|
|
||||||
echo "Backup completed successfully."
|
echo "Backup completed successfully."
|
||||||
|
|
||||||
|
# Remove old backups
|
||||||
|
echo "Removing old backups, keeping last $HISTORY backups..."
|
||||||
|
cd $TARGET_DIR
|
||||||
|
ls -1t *_$DBTYPE_$CONTAINER.sql.zst | tail -n +$((HISTORY + 1)) | xargs -r rm --
|
||||||
|
cd -
|
||||||
|
echo "Old backups removed."
|
||||||
|
|
||||||
|
|
||||||
if [ -n "$NOTIFY_URL" ]; then
|
if [ -n "$NOTIFY_URL" ]; then
|
||||||
curl $NOTIFY_URL
|
curl $NOTIFY_URL
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
#
|
#
|
||||||
# CONTAINER - name of the container where the database is running
|
# CONTAINER - name of the container where the database is running
|
||||||
|
|
@ -22,7 +24,8 @@ if [ -z "$RESTIC_REPOSITORY" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! `docker info` ]; then
|
docker info > /dev/null 2>&1
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
echo "Docker is not available."
|
echo "Docker is not available."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
@ -31,6 +34,7 @@ echo "Starting backup for container: $CONTAINER"
|
||||||
if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || echo "no"` = "yes" ]; then
|
if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || echo "no"` = "yes" ]; then
|
||||||
DBTYPE="mariadb"
|
DBTYPE="mariadb"
|
||||||
docker exec -i $CONTAINER mariadb-dump \
|
docker exec -i $CONTAINER mariadb-dump \
|
||||||
|
--all-databases \
|
||||||
--user=root \
|
--user=root \
|
||||||
--password=$MARIADB_ROOT_PASSWORD \
|
--password=$MARIADB_ROOT_PASSWORD \
|
||||||
--add-drop-trigger \
|
--add-drop-trigger \
|
||||||
|
|
@ -41,10 +45,10 @@ if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || e
|
||||||
--events \
|
--events \
|
||||||
--routines \
|
--routines \
|
||||||
--single-transaction \
|
--single-transaction \
|
||||||
--triggers | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER_$DB_NAME.sql
|
--triggers | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER.sql
|
||||||
elif [ `docker exec -i $CONTAINER test -e /usr/bin/pg_dump && echo "yes" || echo "no"` = "yes" ]; then
|
elif [ `docker exec -i $CONTAINER test -e /usr/bin/pg_dump && echo "yes" || echo "no"` = "yes" ]; then
|
||||||
DBTYPE="pgsql"
|
DBTYPE="pgsql"
|
||||||
docker exec -i $CONTAINER pg_dump --username=$DB_USER --password=$PGPASSWORD $DB_NAME | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER_$DB_NAME.sql
|
docker exec -i $CONTAINER pg_dumpall --username=$DB_USER --password=$PGPASSWORD | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER.sql
|
||||||
else
|
else
|
||||||
echo "Unsupported database type or database client not found in the container."
|
echo "Unsupported database type or database client not found in the container."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue