Initial commit
This commit is contained in:
commit
737e6846e5
6 changed files with 367 additions and 0 deletions
22
.gitea/workflows/main.yml
Normal file
22
.gitea/workflows/main.yml
Normal 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
|
||||
41
.gitea/workflows/release.yml
Normal file
41
.gitea/workflows/release.yml
Normal 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
8
Dockerfile
Normal 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
214
README.md
Normal 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
21
Taskfile.yml
Normal 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
61
backup.sh
Normal 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
|
||||
Loading…
Reference in a new issue