├── .github ├── dependabot.yml └── workflows │ └── docker-image.yml ├── Dockerfile ├── README.md └── entrypoint.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: docker 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - directory: /.github/workflows 8 | package-ecosystem: github-actions 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | env: 9 | IMAGE: minback/postgres 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | if: github.event_name == 'pull_request' 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Build Dockerfile 20 | run: docker build . --file Dockerfile 21 | 22 | push: 23 | 24 | runs-on: ubuntu-latest 25 | if: github.event_name == 'push' 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Build image 31 | run: docker build . --file Dockerfile --tag image 32 | 33 | - name: Log into registries 34 | run: | 35 | echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin 36 | echo "${{ secrets.DOCKER_HUB }}" | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin 37 | 38 | - name: Calculate version number 39 | id: version 40 | run: | 41 | VERSION=$(git describe --tags 2>/dev/null || git rev-parse --short HEAD) 42 | echo "::set-output name=version::$VERSION" 43 | 44 | - name: Push image to GitHub 45 | run: | 46 | IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/${{ github.event.repository.name }} 47 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 48 | docker tag image $IMAGE_ID:latest 49 | docker push $IMAGE_ID:latest 50 | docker tag image $IMAGE_ID:${{ steps.version.outputs.version }} 51 | docker push $IMAGE_ID:${{ steps.version.outputs.version }} 52 | 53 | - name: Push image to Docker Hub 54 | run: | 55 | IMAGE_ID=${{ env.IMAGE }} 56 | echo IMAGE_ID=$IMAGE_ID 57 | docker tag image $IMAGE_ID:latest 58 | docker push $IMAGE_ID:latest 59 | docker tag image $IMAGE_ID:${{ steps.version.outputs.version }} 60 | docker push $IMAGE_ID:${{ steps.version.outputs.version }} 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Fetch the mc command line client 2 | FROM alpine:3.22.0 3 | RUN apk update && apk add ca-certificates wget && update-ca-certificates 4 | RUN wget -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc 5 | RUN chmod +x /tmp/mc 6 | 7 | # Then build our backup image 8 | FROM postgres:17.5-alpine 9 | LABEL maintainer="Benjamin Pannell " 10 | 11 | COPY --from=0 /tmp/mc /usr/bin/mc 12 | 13 | ENV MINIO_SERVER="" 14 | ENV MINIO_BUCKET="backups" 15 | ENV MINIO_ACCESS_KEY="" 16 | ENV MINIO_SECRET_KEY="" 17 | ENV MINIO_API_VERSION="S3v4" 18 | 19 | ENV DATE_FORMAT="+%Y-%m-%d" 20 | 21 | ADD entrypoint.sh /app/entrypoint.sh 22 | RUN chmod +x /app/entrypoint.sh 23 | 24 | ENTRYPOINT [ "/app/entrypoint.sh" ] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minback-postgres 2 | **Minio Backup container for PostgreSQL** 3 | 4 | This container provides a trivially simple means to run `pg_dump` and fire the results off 5 | to a [Minio][] instance. It is intended to be run in conjunction with a [Kubernetes CronJob][] 6 | to maintain a frequent backup of your critical data with minimal fuss. 7 | 8 | ## Features 9 | * Dumps a single PostgreSQL database to an S3 bucket 10 | * Lightweight and short lived 11 | * Simple and readable implementation 12 | 13 | ## Example 14 | ```sh 15 | docker run --rm --env-file backup.env minback/postgres my_db -h pgserver1 16 | ``` 17 | 18 | #### `backup.env` 19 | ``` 20 | MINIO_SERVER=https://play.minio.io/ 21 | MINIO_ACCESS_KEY=minio 22 | MINIO_SECRET_KEY=miniosecret 23 | MINIO_BUCKET=backups 24 | ``` 25 | 26 | ## Usage 27 | ``` 28 | DB_NAME [OPTIONS...] 29 | 30 | Arguments 31 | DB_NAME - The name of the database you wish to backup 32 | OPTIONS - Any additional options you wish to pass to pg_dump 33 | ``` 34 | 35 | ## Configuration 36 | This container is configured using environment variables, enabling it to easily be started 37 | manually or automatically and integrate well with Kubernetes' configuration framework. 38 | 39 | #### `MINIO_SERVER=https://play.minio.io/` 40 | The Minio server you wish to send backups to. 41 | 42 | #### `MINIO_ACCESS_KEY=minio` 43 | The Access Key used to connect to your Minio server. 44 | 45 | #### `MINIO_SECRET_KEY=miniosecret` 46 | The Secret Key used to connect to your Minio server. 47 | 48 | #### `MINIO_BUCKET=backups` 49 | The Minio bucket you wish to store your backup in. 50 | 51 | ### `DATE_FORMAT=+%Y-%m-%d` 52 | The date format you would like to use when naming your backup files. Files are named `$DB-$DATE.archive`. 53 | 54 | [Kubernetes CronJob]: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ 55 | [Minio]: https://minio.io/ 56 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e -o pipefail 3 | 4 | DB="$1" 5 | ARGS=( "${@:2}" ) 6 | 7 | mc alias set pg "$MINIO_SERVER" "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY" --api "$MINIO_API_VERSION" > /dev/null 8 | 9 | ARCHIVE="${MINIO_BUCKET}/${DB}-$(date "$DATE_FORMAT").archive" 10 | 11 | echo "Dumping $DB to $ARCHIVE" 12 | echo "> pg_dump ${ARGS[*]} -Fc $DB" 13 | 14 | pg_dump "${ARGS[@]}" -Fc "$DB" | mc pipe "pg/$ARCHIVE" || { echo "Backup failed"; mc rm "pg/$ARCHIVE"; exit 1; } 15 | 16 | echo "Backup complete" --------------------------------------------------------------------------------