Compare commits
	
		
			7 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d150b7c25e | |||
| 5b172385ca | |||
| 2ab7be67fa | |||
| 5bfbda5e5e | |||
| 89deb6eec9 | |||
| 5698b278b5 | |||
| 4753f6f057 | 
					 6 changed files with 432 additions and 73 deletions
				
			
		| 
						 | 
					@ -1,8 +1,10 @@
 | 
				
			||||||
FROM alpine:3.22
 | 
					FROM alpine:3.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apk add --no-cache restic docker-cli docker-cli-compose curl
 | 
					RUN apk add --no-cache restic docker-cli docker-cli-compose curl zstd bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY backup.sh /backup.sh
 | 
					COPY backup.sh /backup.sh
 | 
				
			||||||
RUN chmod +x /backup.sh
 | 
					COPY backup_local.sh /backup_local.sh
 | 
				
			||||||
 | 
					COPY backup_restic.sh /backup_restic.sh
 | 
				
			||||||
 | 
					RUN chmod +x /backup.sh /backup_local.sh /backup_restic.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT [ "/backup.sh" ]
 | 
					ENTRYPOINT [ "/backup.sh" ]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										285
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										285
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
					@ -1,34 +1,55 @@
 | 
				
			||||||
# Database Backup Container
 | 
					# Database Backup Container
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A lightweight Alpine-based Docker container for backing up MariaDB and PostgreSQL databases using Restic.
 | 
					A lightweight Alpine-based Docker container for backing up MariaDB and PostgreSQL databases with support for both local snapshots and Restic backups.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Overview
 | 
					## 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.
 | 
					This container automatically detects the database type (MariaDB or PostgreSQL) in a target container and creates backups using either local file storage with compression or Restic repositories. It supports backing up databases from other Docker containers by executing dump commands inside them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Multi-database support**: Automatically detects and backs up MariaDB or PostgreSQL databases
 | 
					- **Multi-database support**: Automatically detects and backs up MariaDB or PostgreSQL databases
 | 
				
			||||||
 | 
					- **Dual backup methods**: Choose between local compressed files or Restic repositories
 | 
				
			||||||
 | 
					- **Local snapshots**: Create compressed (zstd) local backup files with timestamps
 | 
				
			||||||
- **Restic integration**: Uses Restic for efficient, encrypted, and deduplicated backups
 | 
					- **Restic integration**: Uses Restic for efficient, encrypted, and deduplicated backups
 | 
				
			||||||
- **Docker-in-Docker**: Can access and backup databases from other containers
 | 
					- **Docker-in-Docker**: Can access and backup databases from other containers
 | 
				
			||||||
 | 
					- **Notification support**: Optional webhook notifications when backups complete
 | 
				
			||||||
- **Lightweight**: Based on Alpine Linux for minimal footprint
 | 
					- **Lightweight**: Based on Alpine Linux for minimal footprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Prerequisites
 | 
					## Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Docker with socket access (`/var/run/docker.sock`)
 | 
					- Docker with socket access (`/var/run/docker.sock`)
 | 
				
			||||||
- Target container with either MariaDB or PostgreSQL client tools
 | 
					- Target container with either MariaDB or PostgreSQL client tools
 | 
				
			||||||
- Restic repository (local, S3, B2, etc.)
 | 
					- For Restic backups: Restic repository (local, S3, B2, etc.)
 | 
				
			||||||
 | 
					- For local backups: Mounted volume for backup storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Environment Variables
 | 
					## Environment Variables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Required
 | 
					### Required (All Methods)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Variable | Description |
 | 
					| Variable | Description |
 | 
				
			||||||
|----------|-------------|
 | 
					|----------|-------------|
 | 
				
			||||||
| `CONTAINER` | Name of the Docker container where the database is running |
 | 
					| `CONTAINER` | Name of the Docker container where the database is running |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### For Restic Backups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Variable | Description |
 | 
				
			||||||
 | 
					|----------|-------------|
 | 
				
			||||||
| `RESTIC_PASSWORD` | Password for the Restic repository |
 | 
					| `RESTIC_PASSWORD` | Password for the Restic repository |
 | 
				
			||||||
| `RESTIC_REPOSITORY` | Restic repository URL (e.g., `s3:s3.amazonaws.com/bucket`, `/data/backups`) |
 | 
					| `RESTIC_REPOSITORY` | Restic repository URL (e.g., `s3:s3.amazonaws.com/bucket`, `/data/backups`) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### For Local Backups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Variable | Description |
 | 
				
			||||||
 | 
					|----------|-------------|
 | 
				
			||||||
 | 
					| `TARGET_DIR` | Directory where backup files will be stored |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| Variable | Description |
 | 
				
			||||||
 | 
					|----------|-------------|
 | 
				
			||||||
 | 
					| `NOTIFY_URL` | Optional webhook URL to call when backup completes |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Database-specific
 | 
					### Database-specific
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### For MariaDB containers:
 | 
					#### For MariaDB containers:
 | 
				
			||||||
| 
						 | 
					@ -45,7 +66,15 @@ This container automatically detects the database type (MariaDB or PostgreSQL) i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Basic Usage
 | 
					### Backup Methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The container supports three execution modes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **`restic`** - Backup to Restic repositories (cloud storage, remote servers)
 | 
				
			||||||
 | 
					2. **`local`** - Create compressed local backup files
 | 
				
			||||||
 | 
					3. **`loop`** - Keep container running for external schedulers (e.g., Ofelia, Kubernetes CronJob)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Restic Backups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
docker run --rm \
 | 
					docker run --rm \
 | 
				
			||||||
| 
						 | 
					@ -54,11 +83,101 @@ docker run --rm \
 | 
				
			||||||
  -e RESTIC_PASSWORD=my-secret-password \
 | 
					  -e RESTIC_PASSWORD=my-secret-password \
 | 
				
			||||||
  -e RESTIC_REPOSITORY=s3:s3.amazonaws.com/my-backup-bucket \
 | 
					  -e RESTIC_REPOSITORY=s3:s3.amazonaws.com/my-backup-bucket \
 | 
				
			||||||
  -e MARIADB_ROOT_PASSWORD=db-password \
 | 
					  -e MARIADB_ROOT_PASSWORD=db-password \
 | 
				
			||||||
  gitea.ceperka.net/rosti/db-backup:latest
 | 
					  gitea.ceperka.net/rosti/db-backup:latest restic
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Local Backups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					docker run --rm \
 | 
				
			||||||
 | 
					  -v /var/run/docker.sock:/var/run/docker.sock \
 | 
				
			||||||
 | 
					  -v /host/backup/path:/backups \
 | 
				
			||||||
 | 
					  -e CONTAINER=my-mariadb-container \
 | 
				
			||||||
 | 
					  -e TARGET_DIR=/backups \
 | 
				
			||||||
 | 
					  -e MARIADB_ROOT_PASSWORD=db-password \
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					version: '3.8'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  database:
 | 
				
			||||||
 | 
					    image: mariadb:latest
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      MARIADB_ROOT_PASSWORD: secretpassword
 | 
				
			||||||
 | 
					      MARIADB_DATABASE: myapp
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - db_data:/var/lib/mysql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  backup-restic:
 | 
				
			||||||
 | 
					    image: gitea.ceperka.net/rosti/db-backup:latest
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - database
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      CONTAINER: database
 | 
				
			||||||
 | 
					      RESTIC_PASSWORD: my-backup-password
 | 
				
			||||||
 | 
					      RESTIC_REPOSITORY: s3:s3.amazonaws.com/my-backup-bucket
 | 
				
			||||||
 | 
					      MARIADB_ROOT_PASSWORD: secretpassword
 | 
				
			||||||
 | 
					      NOTIFY_URL: https://hc-ping.com/your-healthcheck-uuid
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - /var/run/docker.sock:/var/run/docker.sock
 | 
				
			||||||
 | 
					    command: ["restic"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  db_data:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Local Backup Setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					version: '3.8'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  database:
 | 
				
			||||||
 | 
					    image: mariadb:latest
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      MARIADB_ROOT_PASSWORD: secretpassword
 | 
				
			||||||
 | 
					      MARIADB_DATABASE: myapp
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - db_data:/var/lib/mysql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  backup-local:
 | 
				
			||||||
 | 
					    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: ["local"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  db_data:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Loop Mode with External Scheduler (Ofelia)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```yaml
 | 
					```yaml
 | 
				
			||||||
version: '3.8'
 | 
					version: '3.8'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,12 +196,27 @@ services:
 | 
				
			||||||
      - database
 | 
					      - database
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      CONTAINER: database
 | 
					      CONTAINER: database
 | 
				
			||||||
      RESTIC_PASSWORD: my-backup-password
 | 
					      TARGET_DIR: /backups
 | 
				
			||||||
      RESTIC_REPOSITORY: /backups
 | 
					 | 
				
			||||||
      MARIADB_ROOT_PASSWORD: secretpassword
 | 
					      MARIADB_ROOT_PASSWORD: secretpassword
 | 
				
			||||||
 | 
					      NOTIFY_URL: https://hc-ping.com/your-healthcheck-uuid
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - /var/run/docker.sock:/var/run/docker.sock
 | 
					      - /var/run/docker.sock:/var/run/docker.sock
 | 
				
			||||||
      - ./backups:/backups
 | 
					      - ./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:
 | 
					volumes:
 | 
				
			||||||
  db_data:
 | 
					  db_data:
 | 
				
			||||||
| 
						 | 
					@ -92,18 +226,49 @@ volumes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Restic Backups
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Add to crontab for daily backups at 2 AM
 | 
					# Add to crontab for daily Restic 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
 | 
					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=s3:s3.amazonaws.com/bucket -e MARIADB_ROOT_PASSWORD=dbpass gitea.ceperka.net/rosti/db-backup:latest restic
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Local Backups
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Add to crontab for daily local backups at 3 AM
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```yaml
 | 
					```yaml
 | 
				
			||||||
apiVersion: batch/v1
 | 
					apiVersion: batch/v1
 | 
				
			||||||
kind: CronJob
 | 
					kind: CronJob
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: database-backup
 | 
					  name: database-backup-restic
 | 
				
			||||||
spec:
 | 
					spec:
 | 
				
			||||||
  schedule: "0 2 * * *"  # Daily at 2 AM
 | 
					  schedule: "0 2 * * *"  # Daily at 2 AM
 | 
				
			||||||
  jobTemplate:
 | 
					  jobTemplate:
 | 
				
			||||||
| 
						 | 
					@ -113,6 +278,7 @@ spec:
 | 
				
			||||||
          containers:
 | 
					          containers:
 | 
				
			||||||
          - name: backup
 | 
					          - name: backup
 | 
				
			||||||
            image: gitea.ceperka.net/rosti/db-backup:latest
 | 
					            image: gitea.ceperka.net/rosti/db-backup:latest
 | 
				
			||||||
 | 
					            args: ["restic"]
 | 
				
			||||||
            env:
 | 
					            env:
 | 
				
			||||||
            - name: CONTAINER
 | 
					            - name: CONTAINER
 | 
				
			||||||
              value: "my-database-pod"
 | 
					              value: "my-database-pod"
 | 
				
			||||||
| 
						 | 
					@ -138,12 +304,87 @@ spec:
 | 
				
			||||||
          restartPolicy: OnFailure
 | 
					          restartPolicy: OnFailure
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Local Backup CronJob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					apiVersion: batch/v1
 | 
				
			||||||
 | 
					kind: CronJob
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: database-backup-local
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  schedule: "0 3 * * *"  # Daily at 3 AM
 | 
				
			||||||
 | 
					  jobTemplate:
 | 
				
			||||||
 | 
					    spec:
 | 
				
			||||||
 | 
					      template:
 | 
				
			||||||
 | 
					        spec:
 | 
				
			||||||
 | 
					          containers:
 | 
				
			||||||
 | 
					          - name: backup
 | 
				
			||||||
 | 
					            image: gitea.ceperka.net/rosti/db-backup:latest
 | 
				
			||||||
 | 
					            args: ["local"]
 | 
				
			||||||
 | 
					            env:
 | 
				
			||||||
 | 
					            - name: CONTAINER
 | 
				
			||||||
 | 
					              value: "my-database-pod"
 | 
				
			||||||
 | 
					            - name: TARGET_DIR
 | 
				
			||||||
 | 
					              value: "/backups"
 | 
				
			||||||
 | 
					            - name: MARIADB_ROOT_PASSWORD
 | 
				
			||||||
 | 
					              valueFrom:
 | 
				
			||||||
 | 
					                secretKeyRef:
 | 
				
			||||||
 | 
					                  name: db-secrets
 | 
				
			||||||
 | 
					                  key: root-password
 | 
				
			||||||
 | 
					            volumeMounts:
 | 
				
			||||||
 | 
					            - name: docker-sock
 | 
				
			||||||
 | 
					              mountPath: /var/run/docker.sock
 | 
				
			||||||
 | 
					            - name: backup-storage
 | 
				
			||||||
 | 
					              mountPath: /backups
 | 
				
			||||||
 | 
					          volumes:
 | 
				
			||||||
 | 
					          - name: docker-sock
 | 
				
			||||||
 | 
					            hostPath:
 | 
				
			||||||
 | 
					              path: /var/run/docker.sock
 | 
				
			||||||
 | 
					          - name: backup-storage
 | 
				
			||||||
 | 
					            persistentVolumeClaim:
 | 
				
			||||||
 | 
					              claimName: backup-pvc
 | 
				
			||||||
 | 
					          restartPolicy: OnFailure
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Backup File Naming
 | 
					## Backup File Naming
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Restic Backups
 | 
				
			||||||
Backups are stored with the following naming convention:
 | 
					Backups are stored with the following naming convention:
 | 
				
			||||||
- MariaDB: `mariadb_[CONTAINER]_[DB_NAME].sql`
 | 
					- MariaDB: `mariadb_[CONTAINER]_[DB_NAME].sql`
 | 
				
			||||||
- PostgreSQL: `pgsql_[CONTAINER]_[DB_NAME].sql`
 | 
					- PostgreSQL: `pgsql_[CONTAINER]_[DB_NAME].sql`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Local Backups
 | 
				
			||||||
 | 
					Local backup files include timestamps and are compressed:
 | 
				
			||||||
 | 
					- MariaDB: `YYYYMMDD_HHMMSS_mariadb_[CONTAINER]_[DB_NAME].sql.zst`
 | 
				
			||||||
 | 
					- PostgreSQL: `YYYYMMDD_HHMMSS_pgsql_[CONTAINER]_[DB_NAME].sql.zst`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Local backups use zstd compression for efficient storage and include atomic file operations (temporary files are renamed when complete).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## MariaDB Backup Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The container includes comprehensive MariaDB backup options:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **`--add-drop-trigger`** - Add DROP TRIGGER statements
 | 
				
			||||||
 | 
					- **`--add-drop-table`** - Add DROP TABLE statements  
 | 
				
			||||||
 | 
					- **`--add-drop-database`** - Add DROP DATABASE statements
 | 
				
			||||||
 | 
					- **`--hex-blob`** - Use hexadecimal notation for binary data
 | 
				
			||||||
 | 
					- **`--compress`** - Compress data in backup
 | 
				
			||||||
 | 
					- **`--events`** - Include events in backup
 | 
				
			||||||
 | 
					- **`--routines`** - Include stored procedures and functions
 | 
				
			||||||
 | 
					- **`--single-transaction`** - Consistent backup for InnoDB tables
 | 
				
			||||||
 | 
					- **`--triggers`** - Include triggers in backup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Notification Support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Both backup methods support optional webhook notifications:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Set NOTIFY_URL to receive notifications when backups complete
 | 
				
			||||||
 | 
					-e NOTIFY_URL=https://hc-ping.com/your-healthcheck-uuid
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The container will make a GET request to the URL after successful backup completion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Supported Restic Repositories
 | 
					## Supported Restic Repositories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This container supports all Restic repository types:
 | 
					This container supports all Restic repository types:
 | 
				
			||||||
| 
						 | 
					@ -191,14 +432,31 @@ docker build -t gitea.ceperka.net/rosti/db-backup:dev .
 | 
				
			||||||
   - Verify database credentials are correct
 | 
					   - Verify database credentials are correct
 | 
				
			||||||
   - Ensure environment variables are properly set
 | 
					   - Ensure environment variables are properly set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. **"TARGET_DIR does not exist"** (Local backups)
 | 
				
			||||||
 | 
					   - Ensure the target directory is mounted as a volume
 | 
				
			||||||
 | 
					   - Check directory permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. **"Unknown backup method"**
 | 
				
			||||||
 | 
					   - Ensure you specify either `local` or `restic` as the command argument
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Debug Mode
 | 
					### Debug Mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To debug issues, you can run the container interactively:
 | 
					To debug issues, you can run the container interactively:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Restic Debug
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
docker run -it --rm \
 | 
					docker run -it --rm \
 | 
				
			||||||
  -v /var/run/docker.sock:/var/run/docker.sock \
 | 
					  -v /var/run/docker.sock:/var/run/docker.sock \
 | 
				
			||||||
  --entrypoint /bin/sh \
 | 
					  --entrypoint /bin/bash \
 | 
				
			||||||
 | 
					  gitea.ceperka.net/rosti/db-backup:latest
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Local Debug
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					docker run -it --rm \
 | 
				
			||||||
 | 
					  -v /var/run/docker.sock:/var/run/docker.sock \
 | 
				
			||||||
 | 
					  -v /host/backup/path:/backups \
 | 
				
			||||||
 | 
					  --entrypoint /bin/bash \
 | 
				
			||||||
  gitea.ceperka.net/rosti/db-backup:latest
 | 
					  gitea.ceperka.net/rosti/db-backup:latest
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,6 +467,3 @@ docker run -it --rm \
 | 
				
			||||||
- Regularly rotate Restic repository passwords
 | 
					- Regularly rotate Restic repository passwords
 | 
				
			||||||
- Consider using encrypted storage for backup repositories
 | 
					- Consider using encrypted storage for backup repositories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This project is licensed under the MIT License.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,3 +19,6 @@ tasks:
 | 
				
			||||||
  testdb:
 | 
					  testdb:
 | 
				
			||||||
    cmds:
 | 
					    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
 | 
					    - docker run -d --name testmariadb --env MARIADB_USER=maria --env MARIADB_PASSWORD=maria --env MARIADB_DATABASE=maria --env MARIADB_ROOT_PASSWORD=maria mariadb:latest
 | 
				
			||||||
 | 
					  test:
 | 
				
			||||||
 | 
					    cmds:
 | 
				
			||||||
 | 
					    - docker run --name testmariadb-snapshot --rm -e CONTAINER=testmariadb -e TARGET_DIR=/backup -v ./tmp:/backup {{ .IMAGE }}:{{ .TAG }} local
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										68
									
								
								backup.sh
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								backup.sh
									
									
									
									
									
								
							| 
						 | 
					@ -1,61 +1,17 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Environment variables
 | 
					set -e
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# 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
 | 
					if [ "$1" = "local" ]; then
 | 
				
			||||||
    echo "CONTAINER environment variable is not set."
 | 
					    source /backup_local.sh
 | 
				
			||||||
    exit 1
 | 
					elif [ "$1" = "restic" ]; then
 | 
				
			||||||
fi
 | 
					    source /backup_restic.sh
 | 
				
			||||||
 | 
					elif [ "$1" = "loop" ]; then
 | 
				
			||||||
if [ -z "$RESTIC_PASSWORD" ]; then
 | 
					    # Infinite loop to keep the container running and let Ofelia scheduler (or any other) manage the execution
 | 
				
			||||||
    echo "RESTIC_PASSWORD environment variable is not set."
 | 
					    echo "Entering infinite loop mode. Use external scheduler to trigger backups."
 | 
				
			||||||
    exit 1
 | 
					    while true; do
 | 
				
			||||||
fi
 | 
					        sleep 86400
 | 
				
			||||||
 | 
					    done
 | 
				
			||||||
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
 | 
					else
 | 
				
			||||||
    echo "Unsupported database type or database client not found in the container."
 | 
					    echo "Unknown backup method. Use 'local', 'loop or 'restic'."
 | 
				
			||||||
    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
 | 
					fi
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										78
									
								
								backup_local.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								backup_local.sh
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Environment variables
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# CONTAINER - name of the container where the database is running
 | 
				
			||||||
 | 
					# NOTIFY_URL - optional, URL to send notification to
 | 
				
			||||||
 | 
					# TARGET_DIR - directory where to store the backup
 | 
				
			||||||
 | 
					# HISTORY - number of backups to keep (default: 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -z "$CONTAINER" ]; then
 | 
				
			||||||
 | 
					    echo "CONTAINER environment variable is not set."
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -z "$TARGET_DIR" ]; then
 | 
				
			||||||
 | 
					    echo "TARGET_DIR environment variable is not set."
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -z "$HISTORY" ]; then
 | 
				
			||||||
 | 
					    HISTORY=3
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					docker info > /dev/null 2>&1
 | 
				
			||||||
 | 
					if [ ! $? -eq 0 ]; then
 | 
				
			||||||
 | 
					    echo "Docker is not available."
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -d "$TARGET_DIR" ]; then
 | 
				
			||||||
 | 
					    echo "TARGET_DIR does not exist. Creating it..."
 | 
				
			||||||
 | 
					    exit 2
 | 
				
			||||||
 | 
					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"
 | 
				
			||||||
 | 
					    FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER.sql.zst
 | 
				
			||||||
 | 
					    docker exec -i $CONTAINER mariadb-dump \
 | 
				
			||||||
 | 
					        --all-databases \
 | 
				
			||||||
 | 
					        --user=root \
 | 
				
			||||||
 | 
					        --password=$MARIADB_ROOT_PASSWORD \
 | 
				
			||||||
 | 
					        --add-drop-trigger \
 | 
				
			||||||
 | 
					        --add-drop-table \
 | 
				
			||||||
 | 
					        --add-drop-database \
 | 
				
			||||||
 | 
					        --hex-blob \
 | 
				
			||||||
 | 
					        --compress \
 | 
				
			||||||
 | 
					        --events \
 | 
				
			||||||
 | 
					        --routines \
 | 
				
			||||||
 | 
					        --single-transaction \
 | 
				
			||||||
 | 
					        --triggers | zstd -1 > $TARGET_DIR/$FILENAME.tmp
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					    DBTYPE="pgsql"
 | 
				
			||||||
 | 
					    FILENAME=`date +"%Y%m%d_%H%M%S"`_$DBTYPE_$CONTAINER.sql.zst
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    echo "Unsupported database type or database client not found in the container."
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					    curl $NOTIFY_URL
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										65
									
								
								backup_restic.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								backup_restic.sh
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					docker info > /dev/null 2>&1
 | 
				
			||||||
 | 
					if [ ! $? -eq 0 ]; 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 \
 | 
				
			||||||
 | 
					        --all-databases \
 | 
				
			||||||
 | 
					        --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.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_dumpall --username=$DB_USER --password=$PGPASSWORD | restic backup --stdin --stdin-filename=$DBTYPE_$CONTAINER.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