Having a centralized repository (such as Cloud/S3) is crucial for data backup and recovery purposes. It also provides ease when needed to restore the data over different machines/instances. MinIO is one such S3-compatible storage solution that can be deployed anywhere (on-premise, private cloud, etc.), unlike GCP/AWS S3 buckets, which need to be deployed in their respective environments.
In this blog post, we will cover topics like taking backups on MinIO and how we can restore the data over both “local machine” and “k8s/percona postgres operator” based environments. For quick testing in a local setup, MinIO comes in very handy and can be configured with minimal steps.
Let’s discuss this in detail.
Setting up MinIO storage
We are deploying MinIO in a Docker container, which runs over port “9000”. Another port, “9001,” is used for Web console/UI access.
|
1 2 3 4 5 6 7 8 |
shell> docker run -d --name minio -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin -v ~/minio-certs:/root/.minio/certs quay.io/minio/minio server /data --console-address ":9001" |
|
1 2 3 |
shell> docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6e74aede6572 quay.io/minio/minio "/usr/bin/docker-ent…" 2 days ago Up 7 hours 0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp minio |
The Web console is accessed at the address below.
|
1 |
https://127.0.0.1:9001/ |
Here, from the UI itself, we have created a bucket named “ajtest”, which we will use to store/retrieve backups.

Setting up pgBackRest repository in MinIO
|
1 |
shell> cat /opt/homebrew/etc/pgbackrest/pgbackrest.conf |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[global] repo1-type=s3 repo1-s3-bucket=ajtest repo1-s3-endpoint=127.0.0.1 repo1-storage-port=9000 repo1-s3-region=us-east-1 repo1-s3-key=minioadmin repo1-s3-key-secret=minioadmin repo1-s3-uri-style=path repo1-storage-verify-tls=n [pg0app] pg1-path=/opt/homebrew/var/postgresql@17 pg1-port=5432 |
Note: The MinIO user and password (“minioadmin”) will be used as Key/Secrets. The “repo1-s3-region” can be anything, as MinIO is set up locally.
1) Creating the stanza
|
1 2 3 4 |
shell> pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --stanza=pg0app --log-level-console=info stanza-create |
Output:
|
1 2 3 4 |
2025-10-25 13:01:09.525 P00 INFO: stanza-create command begin 2.56.0: --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --exec-id=6328-d2b26a07 --log-level-console=info --pg1-path=/opt/homebrew/var/postgresql@17 --pg1-port=5432 --repo1-s3-bucket=ajtest --repo1-s3-endpoint=127.0.0.1 --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-port=9000 --no-repo1-storage-verify-tls --repo1-type=s3 --stanza=pg0app 2025-10-25 13:01:09.572 P00 INFO: stanza-create for stanza 'pg0app' on repo1 2025-10-25 13:01:09.580 P00 INFO: stanza 'pg0app' already exists on repo1 and is valid 2025-10-25 13:01:09.580 P00 INFO: stanza-create command end: completed successfully (61ms) |
2) Performing pgBackRest health check and connectivity.
|
1 |
shell> pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf check --stanza=pg0app --log-level-console=info |
Output:
|
1 2 3 4 5 6 |
... 2025-10-25 13:05:28.451 P00 INFO: check repo1 configuration (primary) 2025-10-25 13:05:28.765 P00 INFO: check repo1 archive for WAL (primary) 2025-10-25 13:05:29.082 P00 INFO: WAL segment 0000000100000001000000B7 successfully archived to '/var/lib/pgbackrest/archive/pg0app/17-1/0000000100000001/0000000100000001000000B7-fa737ca9ea6b61cda2a67d73608c50e775cee5a8.gz' on repo1 2025-10-25 13:05:29.082 P00 INFO: check command end: completed successfully (655ms) |
3) Taking the backups.
|
1 |
shell> pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf backup --stanza=pg0app --type=full --log-level-console=info |
Output:
|
1 2 3 4 5 6 7 8 9 10 |
2025-10-25 13:12:29.257 P00 INFO: backup command begin 2.56.0: --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --exec-id=7459-777134ce --log-level-console=info --pg1-path=/opt/homebrew/var/postgresql@17 --pg1-port=5432 --repo1-s3-bucket=ajtest --repo1-s3-endpoint=127.0.0.1 --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-port=9000 --no-repo1-storage-verify-tls --repo1-type=s3 --stanza=pg0app --type=full 2025-10-25 13:12:29.257 P00 WARN: option 'repo1-retention-full' is not set for 'repo1-retention-full-type=count', the repository may run out of space HINT: to retain full backups indefinitely (without warning), set option 'repo1-retention-full' to the maximum. 2025-10-25 13:12:29.301 P00 INFO: execute non-exclusive backup start: backup begins after the next regular checkpoint completes 2025-10-25 13:12:29.674 P00 INFO: backup start archive = 0000000100000001000000B9, lsn = 1/B9000060 ... 2025-10-25 13:12:43.706 P00 INFO: backup command end: completed successfully (14455ms) 2025-10-25 13:12:43.706 P00 INFO: expire command begin 2.56.0: --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --exec-id=7459-777134ce --log-level-console=info --repo1-s3-bucket=ajtest --repo1-s3-endpoint=127.0.0.1 --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-port=9000 --no-repo1-storage-verify-tls --repo1-type=s3 --stanza=pg0app 2025-10-25 13:12:43.708 P00 INFO: option 'repo1-retention-archive' is not set - archive logs will not be expired 2025-10-25 13:12:43.708 P00 INFO: expire command end: completed successfully (2ms) |
The backup will be listed in the minIO web console URL.

Also, we can list the backup using the mc-minio tool.
|
1 2 |
shell> mc-minio alias set localminio https://127.0.0.1:9000 minioadmin minioadmin --insecure Added `localminio` successfully. |
|
1 2 3 4 5 6 |
shell> mc-minio --insecure ls localminio/ajtest/var/lib/pgbackrest/backup/pg0app [2025-10-25 13:12:43 IST] 1.7KiB STANDARD backup.info [2025-10-25 13:12:43 IST] 1.7KiB STANDARD backup.info.copy [2025-10-25 13:38:23 IST] 0B 20251023-230455F/ [2025-10-25 13:38:23 IST] 0B 20251025-131229F/ [2025-10-25 13:38:23 IST] 0B backup.history/ |
Similarly, we can also set up pgBackRest with MinIO in the K8s/Percona postgres operator environment.
Setting up pgBackRest repository with MinIO in Percona Operator for PostgreSQL
1) MinIO credentials setup.
|
1 2 3 4 5 |
shell> cat <<EOF | base64 -b 0 [global] repo1-s3-key=minioadmin repo1-s3-key-secret=minioadmin EOF |
Output:
|
1 |
W2dsb2JhbF0KcmVwbzEtczMta2V5PW1pbmlvYWRtaW4KcmVwbzEtczMta2V5LXNlY3JldD1taW5pb2FkbWluCg== |
2) Creating the secret “cluster1-pgbackrest-secrets.yaml”.
|
1 2 3 4 5 6 7 |
apiVersion: v1 kind: Secret metadata: name: cluster1-pgbackrest-secrets type: Opaque data: s3.conf: W2dsb2JhbF0KcmVwbzEtczMta2V5PW1pbmlvCnJlcG8xLXMzLWtleS1zZWNyZXQ9bWluaW8xMjMK |
|
1 |
shell> kubectl apply -f cluster1-pgbackrest-secrets.yaml -n postgres-operator |
3) Adding the bucket, endpoint, and repo details in the “cr.yaml” file.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
... backups: pgbackrest: image: docker.io/percona/percona-pgbackrest:2.55.0 configuration: - secret: name: cluster1-pgbackrest-secrets global: repo1-path: /pgbackrest/postgres-operator/cluster1/repo1 repo1-s3-uri-style: path repo1-s3-verify-tls: 'n' manual: repoName: repo1 options: - --type=full repos: - name: repo1 s3: bucket: "ajtest" endpoint: "https://minio:9000" region: "us-east-1" ... |
|
1 |
shell> Kubectl apply -f cr.yaml -n postgres-operator |
4) Inside the pgBackRest repo pod, we can check the status and perform the backup and other related tasks.
|
1 2 |
shell> kubectl exec -it cluster1-repo-host-0 -n postgres-operator -- sh bash> pgbackrest check --stanza=db --log-level-console=info |
|
1 2 3 4 5 6 7 |
bash> sh-4.4$ pgbackrest backup --type=full --stanza=db --log-level-console=info ... 2025-10-30 17:34:54.699 P00 INFO: backup command end: completed successfully (17516ms) 2025-10-30 17:34:54.699 P00 INFO: expire command begin 2.51: --exec-id=211787-6dd3f744 --log-level-console=info --log-path=/pgbackrest/repo1/log --repo1-path=/pgbackrest/repo1 --stanza=db 2025-10-30 17:34:54.708 P00 INFO: option 'repo1-retention-archive' is not set - archive logs will not be expired 2025-10-30 17:34:54.709 P00 INFO: expire command end: completed successfully (10ms) |
So, till here we performed the pgBackRest backup in MinIO under both local and Percona Operator setups. Next, we will perform restoration similarly in both local and K8s/operator environments.
Restoring pgBackRest in the local environment
1) Stopping the existing database service and cleaning the data directory.
|
1 2 |
shell> brew services stop postgresql@17 shell> rm -rf /opt/homebrew/var/postgresql@17 |
2) Restoring the backup.
|
1 |
shell> pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf restore --stanza=pg0app --log-level-console=info |
Output:
|
1 2 3 4 5 6 7 |
2025-10-25 16:38:49.446 P00 INFO: restore command begin 2.56.0: --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --exec-id=17439-a3384a6b --log-level-console=info --pg1-path=/opt/homebrew/var/postgresql@17 --repo1-s3-bucket=ajtest --repo1-s3-endpoint=127.0.0.1 --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --repo1-storage-port=9000 --no-repo1-storage-verify-tls --repo1-type=s3 --stanza=pg0app 2025-10-25 16:38:49.476 P00 INFO: repo1: restore backup set 20251025-131229F, recovery will start at 2025-10-25 13:12:29 2025-10-25 16:38:49.476 P00 WARN: unknown group 'admin' in backup manifest mapped to current group 2025-10-25 16:38:51.319 P00 INFO: write updated /opt/homebrew/var/postgresql@17/postgresql.auto.conf 2025-10-25 16:38:51.322 P00 INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started) 2025-10-25 16:38:51.322 P00 INFO: restore size = 372MB, file total = 2298 2025-10-25 16:38:51.322 P00 INFO: restore command end: completed successfully (1886ms) |
3) Starting the database service again.
|
1 |
shell> brew services start postgresql@17 |
Note: The required WAL segments for consistent recovery will be fetched from the pgBackRest repository, which is kept in MinIO.
|
1 |
restore_command = '/opt/homebrew/bin/pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --stanza=pg0app archive-get %f "%p"' |
Please also ensure the WAL archiving is in place after restoring the instance.
|
1 |
archive_command = '/opt/homebrew/bin/pgbackrest --config=/opt/homebrew/etc/pgbackrest/pgbackrest.conf --stanza=pg0app --pg1-path=/opt/homebrew/var/postgresql@17 archive-push %p' |
Restoring pgBackRest in K8s/Percona postgres operator environment
1) Create the restore yaml file and apply it.
|
1 |
shell> cat restore.yaml |
|
1 2 3 4 5 6 7 |
piVersion: pgv2.percona.com/v2 kind: PerconaPGRestore metadata: name: restore1 spec: pgCluster: cluster1 repoName: repo1 |
|
1 2 |
shell> kubectl apply -f restore.yaml -n postgres-operator perconapgrestore.pgv2.percona.com/restore1 created |
2) The restore job ran fine as demonstrated below.
|
1 2 3 |
shell> kubectl get pg-restore -n postgres-operator NAME CLUSTER STATUS COMPLETED AGE restore1 cluster1 Succeeded 76s 93s |
For more details about the restore job, refer to the command below.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
shell> kubectl describe pg-restore restore1 -n postgres-operator Name: restore1 Namespace: postgres-operator Labels: <none> Annotations: <none> API Version: pgv2.percona.com/v2 Kind: PerconaPGRestore Metadata: Creation Timestamp: 2025-10-26T05:28:22Z Finalizers: internal.percona.com/delete-restore Generation: 1 Resource Version: 2097013 UID: 43ee4fb5-14c0-4776-92c1-7d52a67b8934 Spec: Pg Cluster: cluster1 Repo Name: repo1 Status: Completed: 2025-10-26T05:28:39Z State: Succeeded Events: <none> |
3) The postgres pod will be reinitialized with the new restoration and ready to be used.
|
1 2 3 4 |
shell> kubectl get pods -n postgres-operator ... cluster1-instance1-g68c-0 4/4 Running 0 10h ... |
We can verify the data by simplifying the login inside the respective pod and service.
|
1 2 3 |
shell> kubectl exec -it cluster1-instance1-g68c-0 -n postgres-operator -- sh bash> psql ... |
Note: The restore described above for the PostgreSQL operator will automatically overwrite the existing cluster data. Therefore, we need to be extra cautious, or, if needed, take existing backups before performing any restorations.
Conclusion
MinIO buckets provide an excellent way to test and manage S3-style repositories without having any vendor-specific (AWS/GCP) setup. It supports both traditional and Kubernetes/operator-based environments efficiently. Combined with pgBackRest, PostgreSQL backups/recovery are made quite easy and accessible, almost instant when having a centralized backup repository.
