commit 737e6846e54ef22d226ac0aecb5e965784bd33cb Author: Adam Štrauch Date: Sat Oct 18 01:48:43 2025 +0200 Initial commit diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml new file mode 100644 index 0000000..c13f16e --- /dev/null +++ b/.gitea/workflows/main.yml @@ -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 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..dd9deae --- /dev/null +++ b/.gitea/workflows/release.yml @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b6f261 --- /dev/null +++ b/Dockerfile @@ -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" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..23e7403 --- /dev/null +++ b/README.md @@ -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. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..0bd3bc1 --- /dev/null +++ b/Taskfile.yml @@ -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 diff --git a/backup.sh b/backup.sh new file mode 100644 index 0000000..daeebee --- /dev/null +++ b/backup.sh @@ -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