Initial commit
All checks were successful
Build a dev image / build (push) Successful in 13s
Release of a new version / build (release) Successful in 17s

This commit is contained in:
Adam Štrauch 2025-10-18 01:48:43 +02:00
commit 737e6846e5
Signed by: cx
GPG key ID: 7262DAFE292BCE20
6 changed files with 367 additions and 0 deletions

22
.gitea/workflows/main.yml Normal file
View file

@ -0,0 +1,22 @@
name: Build a dev image
on:
push:
branches: [ main ]
workflow_dispatch: {}
jobs:
build:
runs-on: [dev, amd64]
env:
IMAGE: gitea.ceperka.net/rosti/database-backup
TAG: dev
steps:
- uses: actions/checkout@v4
- name: docker login
run: |
docker login gitea.ceperka.net -u "${{ secrets.REGISTRY_DEV_USERNAME }}" -p "${{ secrets.REGISTRY_DEV_PASSWORD }}"
- name: Build
run: task build IMAGE=$IMAGE TAG=$TAG
- name: Push
run: task push IMAGE=$IMAGE TAG=$TAG

View file

@ -0,0 +1,41 @@
name: Release of a new version
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to release'
required: true
default: 'latest'
type: string
jobs:
build:
runs-on: [dev, amd64]
env:
IMAGE: harbor.rosti.cz/rosti-public/database-backup
steps:
- uses: actions/checkout@v4
# Figure out the tag
- name: Get git tag
id: get_tag
if: github.event_name == 'release'
run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Set version from input
if: github.event_name == 'workflow_dispatch'
run: echo "TAG_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: docker login
run: |
docker login harbor.rosti.cz -u "${{ secrets.REGISTRY_PROD_USERNAME }}" -p "${{ secrets.REGISTRY_PROD_PASSWORD }}"
- name: Build
run: task build IMAGE=$IMAGE TAG=${{ env.TAG_NAME }}
- name: Tag latest
run: task tag-latest IMAGE=$IMAGE TAG=${{ env.TAG_NAME }}
- name: Push
run: task push IMAGE=$IMAGE TAG=${{ env.TAG_NAME }}
- name: Push latest
run: task push IMAGE=$IMAGE TAG=latest

8
Dockerfile Normal file
View file

@ -0,0 +1,8 @@
FROM alpine:3.22
RUN apk add --no-cache restic docker-cli docker-cli-compose curl
COPY backup.sh /backup.sh
RUN chmod +x /backup.sh
ENTRYPOINT [ "/backup.sh" ]

214
README.md Normal file
View file

@ -0,0 +1,214 @@
# Database Backup Container
A lightweight Alpine-based Docker container for backing up MariaDB and PostgreSQL databases using Restic.
## Overview
This container automatically detects the database type (MariaDB or PostgreSQL) in a target container and creates backups using Restic. It supports backing up databases from other Docker containers by executing dump commands inside them.
## Features
- **Multi-database support**: Automatically detects and backs up MariaDB or PostgreSQL databases
- **Restic integration**: Uses Restic for efficient, encrypted, and deduplicated backups
- **Docker-in-Docker**: Can access and backup databases from other containers
- **Lightweight**: Based on Alpine Linux for minimal footprint
## Prerequisites
- Docker with socket access (`/var/run/docker.sock`)
- Target container with either MariaDB or PostgreSQL client tools
- Restic repository (local, S3, B2, etc.)
## Environment Variables
### Required
| Variable | Description |
|----------|-------------|
| `CONTAINER` | Name of the Docker container where the database is running |
| `RESTIC_PASSWORD` | Password for the Restic repository |
| `RESTIC_REPOSITORY` | Restic repository URL (e.g., `s3:s3.amazonaws.com/bucket`, `/data/backups`) |
### Database-specific
#### For MariaDB containers:
| Variable | Description |
|----------|-------------|
| `MARIADB_ROOT_PASSWORD` | Root password for MariaDB |
#### For PostgreSQL containers:
| Variable | Description |
|----------|-------------|
| `DB_USER` | PostgreSQL username |
| `PGPASSWORD` | PostgreSQL password |
| `DB_NAME` | PostgreSQL database name |
## Usage
### Basic Usage
```bash
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-e CONTAINER=my-mariadb-container \
-e RESTIC_PASSWORD=my-secret-password \
-e RESTIC_REPOSITORY=s3:s3.amazonaws.com/my-backup-bucket \
-e MARIADB_ROOT_PASSWORD=db-password \
gitea.ceperka.net/rosti/db-backup:latest
```
### With Docker Compose
```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
RESTIC_PASSWORD: my-backup-password
RESTIC_REPOSITORY: /backups
MARIADB_ROOT_PASSWORD: secretpassword
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./backups:/backups
volumes:
db_data:
```
### Scheduled Backups with Cron
To run backups on a schedule, you can use cron or a container orchestrator:
```bash
# Add to crontab for daily backups at 2 AM
0 2 * * * docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -e CONTAINER=my-db -e RESTIC_PASSWORD=pass -e RESTIC_REPOSITORY=/backups -e MARIADB_ROOT_PASSWORD=dbpass gitea.ceperka.net/rosti/db-backup:latest
```
### Kubernetes CronJob
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "0 2 * * *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: gitea.ceperka.net/rosti/db-backup:latest
env:
- name: CONTAINER
value: "my-database-pod"
- name: RESTIC_PASSWORD
valueFrom:
secretKeyRef:
name: backup-secrets
key: restic-password
- name: RESTIC_REPOSITORY
value: "s3:s3.amazonaws.com/my-backup-bucket"
- name: MARIADB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-secrets
key: root-password
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
restartPolicy: OnFailure
```
## Backup File Naming
Backups are stored with the following naming convention:
- MariaDB: `mariadb_[CONTAINER]_[DB_NAME].sql`
- PostgreSQL: `pgsql_[CONTAINER]_[DB_NAME].sql`
## Supported Restic Repositories
This container supports all Restic repository types:
- **Local**: `/path/to/backup/dir`
- **SFTP**: `sftp:user@host:/path/to/repo`
- **S3**: `s3:s3.amazonaws.com/bucket`
- **Azure**: `azure:container:/path`
- **Google Cloud**: `gs:bucket:/path`
- **Backblaze B2**: `b2:bucket:/path`
- **REST**: `rest:http://host:8000/repo`
## Building
```bash
# Build the image
task build
# Tag as latest
task tag-latest
# Push to registry
task push
```
Or manually:
```bash
docker build -t gitea.ceperka.net/rosti/db-backup:dev .
```
## Troubleshooting
### Common Issues
1. **"Docker is not available"**
- Ensure Docker socket is mounted: `-v /var/run/docker.sock:/var/run/docker.sock`
- Check Docker daemon is running
2. **"Unsupported database type"**
- Verify the target container has `mariadb-dump` or `pg_dump` installed
- Check container name is correct
3. **Authentication errors**
- Verify database credentials are correct
- Ensure environment variables are properly set
### Debug Mode
To debug issues, you can run the container interactively:
```bash
docker run -it --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
--entrypoint /bin/sh \
gitea.ceperka.net/rosti/db-backup:latest
```
## Security Considerations
- Store sensitive environment variables in secrets (Kubernetes secrets, Docker secrets, etc.)
- Use least-privilege access for Docker socket when possible
- Regularly rotate Restic repository passwords
- Consider using encrypted storage for backup repositories
## License
This project is licensed under the MIT License.

21
Taskfile.yml Normal file
View file

@ -0,0 +1,21 @@
# https://taskfile.dev
version: '3'
vars:
IMAGE: gitea.ceperka.net/rosti/db-backup
TAG: dev
tasks:
build:
cmds:
- docker build -t {{ .IMAGE }}:{{ .TAG }} .
tag-latest:
cmds:
- docker tag {{ .IMAGE }}:{{ .TAG }} {{ .IMAGE }}:latest
push:
cmds:
- docker push {{ .IMAGE }}:{{ .TAG }}
testdb:
cmds:
- docker run -d --name testmariadb --env MARIADB_USER=maria --env MARIADB_PASSWORD=maria --env MARIADB_DATABASE=maria --env MARIADB_ROOT_PASSWORD=maria mariadb:latest

61
backup.sh Normal file
View file

@ -0,0 +1,61 @@
#!/bin/bash
# Environment variables
#
# CONTAINER - name of the container where the database is running
# RESTIC_PASSWORD
# RESTIC_REPOSITORY
# NOTIFY_URL - optional, URL to send notification to
if [ -z "$CONTAINER" ]; then
echo "CONTAINER environment variable is not set."
exit 1
fi
if [ -z "$RESTIC_PASSWORD" ]; then
echo "RESTIC_PASSWORD environment variable is not set."
exit 1
fi
if [ -z "$RESTIC_REPOSITORY" ]; then
echo "RESTIC_REPOSITORY environment variable is not set."
exit 1
fi
if [ ! `docker info` ]; then
echo "Docker is not available."
exit 1
fi
echo "Starting backup for container: $CONTAINER"
if [ `docker exec -i $CONTAINER test -e /usr/bin/mariadb-dump && echo "yes" || echo "no"` = "yes" ]; then
DBTYPE="mariadb"
docker exec -i $CONTAINER mariadb-dump \
--user=root \
--password=$MARIADB_ROOT_PASSWORD \
--add-drop-trigger \
--add-drop-table \
--add-drop-database \
--hex-blob \
--compress \
--events \
--routines \
--single-transaction \
--triggers | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER_$DB_NAME.sql
elif [ `docker exec -i $CONTAINER test -e /usr/bin/pg_dump && echo "yes" || echo "no"` = "yes" ]; then
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
else
echo "Unsupported database type or database client not found in the container."
exit 1
fi
echo "Backup completed successfully."
echo "Running restic forget and prune..."
restic forget --prune --keep-daily 7 --keep-weekly 4
if [ -n "$NOTIFY_URL" ]; then
curl $NOTIFY_URL
fi