Back up a database to a bucket with a CronJob
Cloider already backs up every managed database, with a full backup every 14 days plus every change in between,
so you can restore any second of the last 14 days into a new database. This guide adds
backups that you own: a nightly logical dump (pg_dump for
PostgreSQL, mysqldump for MySQL) that runs as a Kubernetes CronJob on a Cloider cluster and is uploaded to a
Cloider bucket, kept as long as you decide, downloadable as a file, and restorable anywhere. You also test a
restore, because a backup is only proven by restoring it.
At a glance
| You will build | A CronJob orders-db-backup in the namespace backups of the cluster production that writes orders-db-<time>.dump to acme-backups/postgres/orders-db/ every night at 03:30 UTC, with backups expiring after 30 days |
| Products | Databases, Kubernetes, Object Storage |
| Tools | The cloider CLI, kubectl. For local restores, pg_restore (PostgreSQL 18 client) or mysql |
| Difficulty | Intermediate. You should know what a Kubernetes Secret and a Job are |
| Time | About 45 minutes |
| Cost | A few minutes of cluster compute per night, bucket storage for the dumps. See What it costs |
Why back up a database yourself?
| Cloider's backups | Your own dumps in a bucket (this guide) | |
|---|---|---|
| Setup | None | One CronJob |
| Retention | The last 14 days, any second of them | As long as you decide, with a lifecycle rule |
| Restore | Into a new Cloider database, in one click or command | Anywhere: a Cloider database, another provider, your laptop |
| Download a file | No | Yes |
| Survives deleting the database | No, backups are deleted with it | Yes, they are in a separate bucket |
| Schedule | A full backup every 14 days, every change recorded in between | Any cron schedule |
Use both: the built-in backups for a quick restore of recent data, to the second, and your own dumps for long retention, compliance, migrations and protection against deleting the database by mistake. See Backups and restore.
What you'll build
- At 03:30 UTC Kubernetes starts a Job pod.
- The init container runs
pg_dumpagainst the database's private host and writes a compressed dump to a scratch volume, then checks that the file is a readable archive. - The main container runs only if the dump succeeded, and uploads it to the bucket with rclone.
- A lifecycle rule on the bucket deletes dumps older than 30 days.
Before you begin
-
A Cloider account and the CLI, logged in with Read & Write access, and
kubectl. See Install the CLI. -
A database to back up. This guide uses the PostgreSQL database
orders-dband shows the MySQL databaseshop-dbin the MySQL tabs. To follow along from scratch:cloider db create orders-db --engine postgresql --wait -
A Kubernetes cluster for the CronJob. To create one:
cloider cluster create production --waitcloider cluster kubeconfig production --merge --useOn the free trial (one cluster, one database, one bucket at a time) this guide fits exactly, except the restore test in part 6, which creates a second database. On the trial, restore into a PostgreSQL or MySQL server on your own computer instead, with the same commands.
Part 1: Create the backup bucket and its key
Create a private, versioned bucket for the dumps. Versioning keeps a dump that is overwritten or deleted by mistake recoverable:
cloider bucket create acme-backups --versioning --wait
✓ Created bucket acme-backups
✓ Bucket acme-backups is ready
Bucket names are shared by every Cloider account: choose your own if acme-backups is taken, and use it everywhere
below.
Add a lifecycle rule that deletes dumps 30 days after they are written, removes replaced versions after 7 days, and cleans up unfinished uploads:
[
{"id": "expire-dumps", "prefix": "postgres/", "enabled": true, "expire_days": 30, "noncurrent_days": 7, "abort_multipart_days": 1},
{"id": "expire-mysql-dumps", "prefix": "mysql/", "enabled": true, "expire_days": 30, "noncurrent_days": 7, "abort_multipart_days": 1}
]
cloider bucket lifecycle set acme-backups -f backup-rules.json
✓ acme-backups now has 2 lifecycle rule(s)
Then create an access key that only reaches this bucket. --print-env prints the key as shell variables, which you load
straight into this shell for part 3:
eval "$(cloider bucket key create --label backup-cronjob --bucket acme-backups --print-env)"
echo "$AWS_ENDPOINT_URL $AWS_REGION $AWS_ACCESS_KEY_ID"
! The secret key is not shown again, keep it now.
https://s3.eu-central-2.cloider.com eu-central-2 CLDRZ8Q4M2X7KD1PLW0B
The secret key is in AWS_SECRET_ACCESS_KEY in this shell only. See Lifecycle rules
and Access keys.
Verify
cloider bucket lifecycle get acme-backups
cloider bucket key list
PREFIX ENABLED EXPIRE NONCURRENT ABORT UPLOADS
postgres/ yes 30d 7d 1d
mysql/ yes 30d 7d 1d
ACCESS KEY LABEL BUCKETS PERMISSION STATUS CREATED
CLDRZ8Q4M2X7KD1PLW0B backup-cronjob acme-backups Read & Write Ready 2026-10-21 09:12
Part 2: Let the cluster reach the database
The CronJob connects to the database's private host from the cluster, so the database must trust the cluster:
- PostgreSQL
- MySQL
cloider db trusted-sources orders-db --add cluster:production
cloider db trusted-sources shop-db --add cluster:production
✓ Updated who may connect to orders-db
SOURCE TYPE NAME
x8f2kq Cluster production
If the list was * (anywhere), adding the cluster replaces it, so the database now accepts only the cluster. Add other
sources your apps need with more --add flags. See Trusted sources.
Verify
cloider db get orders-db shows cluster:production under Trusted sources.
Part 3: Store the credentials in the cluster
Create the namespace and two Secrets: the database login for the dump, and the bucket key for rclone. The password is read straight from the CLI, and the key from the variables part 1 set, so nothing is pasted by hand.
- PostgreSQL
- MySQL
kubectl create namespace backups
DB_JSON=$(cloider db get orders-db -o json)
kubectl create secret generic orders-db-login -n backups \
--from-literal=PGHOST="$(echo "$DB_JSON" | jq -r .private_host)" \
--from-literal=PGPORT="$(echo "$DB_JSON" | jq -r .database_port)" \
--from-literal=PGUSER="$(echo "$DB_JSON" | jq -r .database_user)" \
--from-literal=PGDATABASE="$(echo "$DB_JSON" | jq -r .database_name)" \
--from-literal=PGPASSWORD="$(cloider db password orders-db)"
kubectl create secret generic backup-bucket -n backups \
--from-literal=RCLONE_CONFIG_CLOIDER_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
--from-literal=RCLONE_CONFIG_CLOIDER_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
--from-literal=RCLONE_CONFIG_CLOIDER_ENDPOINT="$AWS_ENDPOINT_URL" \
--from-literal=RCLONE_CONFIG_CLOIDER_REGION="$AWS_REGION"
PGHOST, PGPORT, PGUSER, PGDATABASE and PGPASSWORD are the standard variables every PostgreSQL client reads,
so pg_dump needs no connection flags.
kubectl create namespace backups
DB_JSON=$(cloider db get shop-db -o json)
kubectl create secret generic shop-db-login -n backups \
--from-literal=MYSQL_HOST="$(echo "$DB_JSON" | jq -r .private_host)" \
--from-literal=MYSQL_TCP_PORT="$(echo "$DB_JSON" | jq -r .database_port)" \
--from-literal=MYSQL_USER="$(echo "$DB_JSON" | jq -r .database_user)" \
--from-literal=MYSQL_DATABASE="$(echo "$DB_JSON" | jq -r .database_name)" \
--from-literal=MYSQL_PWD="$(cloider db password shop-db)"
kubectl create secret generic backup-bucket -n backups \
--from-literal=RCLONE_CONFIG_CLOIDER_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \
--from-literal=RCLONE_CONFIG_CLOIDER_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \
--from-literal=RCLONE_CONFIG_CLOIDER_ENDPOINT="$AWS_ENDPOINT_URL" \
--from-literal=RCLONE_CONFIG_CLOIDER_REGION="$AWS_REGION"
MYSQL_HOST, MYSQL_TCP_PORT and MYSQL_PWD are read by the MySQL client tools. MYSQL_USER and MYSQL_DATABASE
are passed as arguments in the manifest.
The RCLONE_CONFIG_CLOIDER_* variables define an rclone remote called cloider without a config file. See
S3 tools.
Verify
kubectl get secrets -n backups
NAME TYPE DATA AGE
backup-bucket Opaque 4 8s
orders-db-login Opaque 5 9s
Part 4: Create the CronJob
- PostgreSQL
- MySQL
apiVersion: batch/v1
kind: CronJob
metadata:
name: orders-db-backup
namespace: backups
spec:
schedule: "30 3 * * *"
timeZone: "Etc/UTC"
# Never run two backups at once
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600
template:
spec:
restartPolicy: Never
volumes:
- name: backup
emptyDir: {}
initContainers:
# 1. Dump the database. pg_dump's major version must be at least the server's (18).
- name: dump
image: postgres:18-alpine
envFrom:
- secretRef:
name: orders-db-login
env:
- name: PGSSLMODE
value: require
command: ["/bin/sh", "-c"]
args:
- |
set -eu
FILE="/backup/orders-db-$(date -u +%Y-%m-%dT%H-%M-%SZ).dump"
pg_dump --format=custom --no-owner --no-privileges --file="$FILE"
# Fail here if the archive can't be read back
pg_restore --list "$FILE" > /dev/null
ls -l "$FILE"
volumeMounts:
- name: backup
mountPath: /backup
resources:
requests:
cpu: 100m
memory: 128Mi
containers:
# 2. Upload it. Runs only if the dump succeeded.
- name: upload
image: rclone/rclone:1.71
envFrom:
- secretRef:
name: backup-bucket
env:
- name: RCLONE_CONFIG_CLOIDER_TYPE
value: s3
- name: RCLONE_CONFIG_CLOIDER_PROVIDER
value: Other
- name: RCLONE_CONFIG_CLOIDER_NO_CHECK_BUCKET
value: "true"
args: ["copy", "/backup", "cloider:acme-backups/postgres/orders-db/", "-v"]
volumeMounts:
- name: backup
mountPath: /backup
resources:
requests:
cpu: 50m
memory: 64Mi
apiVersion: batch/v1
kind: CronJob
metadata:
name: shop-db-backup
namespace: backups
spec:
schedule: "30 3 * * *"
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600
template:
spec:
restartPolicy: Never
volumes:
- name: backup
emptyDir: {}
initContainers:
# 1. Dump the database with a client of the server's major version (9)
- name: dump
image: mysql:9
envFrom:
- secretRef:
name: shop-db-login
command: ["/bin/sh", "-c"]
args:
- |
set -eu
FILE="/backup/shop-db-$(date -u +%Y-%m-%dT%H-%M-%SZ).sql"
mysqldump --user="$MYSQL_USER" --ssl-mode=REQUIRED \
--single-transaction --no-tablespaces --routines --triggers \
--set-gtid-purged=OFF --result-file="$FILE" "$MYSQL_DATABASE"
# A complete dump ends with this line
tail -n 1 "$FILE" | grep -q "Dump completed"
ls -l "$FILE"
volumeMounts:
- name: backup
mountPath: /backup
resources:
requests:
cpu: 100m
memory: 256Mi
containers:
# 2. Compress and upload it
- name: upload
image: rclone/rclone:1.71
envFrom:
- secretRef:
name: backup-bucket
env:
- name: RCLONE_CONFIG_CLOIDER_TYPE
value: s3
- name: RCLONE_CONFIG_CLOIDER_PROVIDER
value: Other
- name: RCLONE_CONFIG_CLOIDER_NO_CHECK_BUCKET
value: "true"
command: ["/bin/sh", "-c"]
args:
- gzip /backup/*.sql && rclone copy /backup cloider:acme-backups/mysql/shop-db/ -v
volumeMounts:
- name: backup
mountPath: /backup
resources:
requests:
cpu: 50m
memory: 64Mi
--single-transaction takes a consistent snapshot of InnoDB tables without locking them, and --no-tablespaces
avoids a privilege the database user doesn't have. See MySQL.
Why it is built this way:
- Init container, then main container. Kubernetes runs the upload only after the dump exits successfully, so a failed dump never uploads a half written file, and the Job fails visibly instead.
emptyDiris scratch space that lives as long as the pod. It is billed as part of the pod, not as a persistent volume. For very large databases, see Troubleshooting.- A timestamp in every file name, so each run adds a new object and nothing is overwritten.
concurrencyPolicy: Forbidskips a run if the previous one is still going.activeDeadlineSecondsstops a stuck backup after an hour.timeZone: Etc/UTCmakes the schedule read the same everywhere. Pick any time, ideally one when the database is quiet.
Apply it:
kubectl apply -f orders-db-backup.yaml
cronjob.batch/orders-db-backup created
Verify
kubectl get cronjob -n backups
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
orders-db-backup 30 3 * * * Etc/UTC False 0 <none> 5s
Part 5: Run a backup now
Don't wait for the night: start a Job from the CronJob's template and follow it.
kubectl create job orders-db-backup-manual --from=cronjob/orders-db-backup -n backups
kubectl wait --for=condition=complete job/orders-db-backup-manual -n backups --timeout=15m
kubectl logs job/orders-db-backup-manual -n backups -c dump
kubectl logs job/orders-db-backup-manual -n backups -c upload
job.batch/orders-db-backup-manual created
job.batch/orders-db-backup-manual condition met
-rw-r--r-- 1 root root 4817203 Oct 21 09:31 /backup/orders-db-2026-10-21T09-30-58Z.dump
2026/10/21 09:31:04 INFO : orders-db-2026-10-21T09-30-58Z.dump: Copied (new)
2026/10/21 09:31:04 INFO :
Transferred: 4.594 MiB / 4.594 MiB, 100%, 0 B/s, ETA -
Transferred: 1 / 1, 100%
Elapsed time: 1.2s
Verify
The dump is in the bucket:
cloider bucket ls acme-backups postgres/orders-db/
KEY SIZE MODIFIED
postgres/orders-db/orders-db-2026-10-21T09-30-58Z.dump 4.6 MB 2026-10-21 09:31
You can also browse it in the dashboard, on the bucket's Files tab.
Clean up the manual Job when you are done looking at it: kubectl delete job orders-db-backup-manual -n backups.
Part 6: Restore a backup
Practice a restore now, while nothing is on fire, and repeat it regularly (for example monthly). There are three ways, from safest to most invasive.
Restore into a new database (recommended)
Create an empty database, download the dump, and load it. Your production database is never touched, and you can compare the two before switching anything over.
- PostgreSQL
- MySQL
# A new database, reachable from this computer
cloider db create orders-db-restore --engine postgresql --allow-my-ip --wait
# The newest dump
KEY=$(cloider bucket ls acme-backups postgres/orders-db/ -o json | jq -r '.objects | map(.key) | sort | last')
cloider bucket download acme-backups "$KEY" -d ./
# Load it, as the new database's user
RESTORE_JSON=$(cloider db get orders-db-restore -o json)
PGPASSWORD="$(cloider db password orders-db-restore)" PGSSLMODE=require \
pg_restore --no-owner --no-privileges --jobs 4 \
-h "$(echo "$RESTORE_JSON" | jq -r .host)" \
-p "$(echo "$RESTORE_JSON" | jq -r .database_port)" \
-U "$(echo "$RESTORE_JSON" | jq -r .database_user)" \
-d "$(echo "$RESTORE_JSON" | jq -r .database_name)" \
"$(basename "$KEY")"
pg_restore must be version 18 or later, the version of the dump. --no-owner --no-privileges makes every object
belong to the new database's user.
cloider db create shop-db-restore --engine mysql --allow-my-ip --wait
KEY=$(cloider bucket ls acme-backups mysql/shop-db/ -o json | jq -r '.objects | map(.key) | sort | last')
cloider bucket download acme-backups "$KEY" -d ./
RESTORE_JSON=$(cloider db get shop-db-restore -o json)
gunzip -c "$(basename "$KEY")" | \
MYSQL_PWD="$(cloider db password shop-db-restore)" mysql --ssl-mode=REQUIRED \
-h "$(echo "$RESTORE_JSON" | jq -r .host)" \
-P "$(echo "$RESTORE_JSON" | jq -r .database_port)" \
-u "$(echo "$RESTORE_JSON" | jq -r .database_user)" \
"$(echo "$RESTORE_JSON" | jq -r .database_name)"
A small dump (under 2 MiB uncompressed) can also be loaded without any local client:
gunzip -c dump.sql.gz | cloider db exec -i shop-db-restore.
cloider bucket ls -o json prints the folder's objects with their key, size and last_modified, and the timestamp in each file name makes the newest one sort last. If you have aws or rclone set up with a
key for the bucket, they download just as well. See Manage files.
Restore inside the cluster
For large dumps, or without the client tools on your computer, run the restore as a Job in the cluster. It downloads
the dump with rclone into scratch space and runs pg_restore there, against a target database the cluster can reach:
# The target's login, like orders-db-login in part 3
DB_JSON=$(cloider db get orders-db-restore -o json)
cloider db trusted-sources orders-db-restore --add cluster:production
kubectl create secret generic orders-db-restore-login -n backups \
--from-literal=PGHOST="$(echo "$DB_JSON" | jq -r .private_host)" \
--from-literal=PGPORT="$(echo "$DB_JSON" | jq -r .database_port)" \
--from-literal=PGUSER="$(echo "$DB_JSON" | jq -r .database_user)" \
--from-literal=PGDATABASE="$(echo "$DB_JSON" | jq -r .database_name)" \
--from-literal=PGPASSWORD="$(cloider db password orders-db-restore)"
apiVersion: batch/v1
kind: Job
metadata:
name: orders-db-restore
namespace: backups
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
volumes:
- name: backup
emptyDir: {}
initContainers:
- name: download
image: rclone/rclone:1.71
envFrom:
- secretRef:
name: backup-bucket
env:
- name: RCLONE_CONFIG_CLOIDER_TYPE
value: s3
- name: RCLONE_CONFIG_CLOIDER_PROVIDER
value: Other
- name: RCLONE_CONFIG_CLOIDER_NO_CHECK_BUCKET
value: "true"
# The dump to restore, as listed by cloider bucket ls
args: ["copyto", "cloider:acme-backups/postgres/orders-db/orders-db-2026-10-21T09-30-58Z.dump", "/backup/restore.dump", "-v"]
volumeMounts:
- name: backup
mountPath: /backup
containers:
- name: restore
image: postgres:18-alpine
envFrom:
- secretRef:
name: orders-db-restore-login
env:
- name: PGSSLMODE
value: require
command: ["pg_restore", "--no-owner", "--no-privileges", "--jobs=4", "--dbname=$(PGDATABASE)", "/backup/restore.dump"]
volumeMounts:
- name: backup
mountPath: /backup
kubectl apply -f orders-db-restore-job.yaml
kubectl wait --for=condition=complete job/orders-db-restore -n backups --timeout=30m
kubectl logs job/orders-db-restore -n backups -c restore
Restore over the original database (last resort)
To put the data back into orders-db itself, run the same pg_restore against it with --clean --if-exists, which
drops each object before recreating it. Everything written since the dump is lost, so stop the apps that write to it
first, and take a fresh dump right before you start. Most of the time, restoring into a new database and pointing the
app at it is the safer move.
Verify
Compare the restored data with production:
cloider db exec orders-db "SELECT count(*), max(created_at) FROM orders"
cloider db exec orders-db-restore "SELECT count(*), max(created_at) FROM orders"
The counts match the time of the dump: rows written after 09:30 UTC are only in production.
Watch for failed backups
A backup job that fails quietly is worse than none. Check it from time to time, or wire it into your monitoring:
kubectl get jobs -n backups
kubectl get cronjob orders-db-backup -n backups -o jsonpath='{.status.lastSuccessfulTime}{"\n"}'
- A Job whose
COMPLETIONSstays0/1failed.kubectl logs job/<name> -n backups -c dumpshows why. lastSuccessfulTimeolder than a day means the last nights failed.- For alerts, add a last step to the upload container that calls a dead man's switch service (a URL that alerts you
when it is not called), for example
&& wget -q -O- https://hc.example.com/ping/<uuid>after therclone copy.
Harden the backups
- A key that can't delete. The
backup-cronjobkey only needs to add files. A bucket policy can deny its3:DeleteObject, and deny everyone deleting versions or changing versioning, so a leaked key can't destroy the backups. The full recipe is in Data protection. - More engines. MongoDB works the same way with
mongodump --uri ... --gzip --archive=/backup/...in amongoimage; Redis withredis-cli --rdb /backup/dump.rdb. Use the database's private host and, with SSL on, its CA certificate (cloider db cert <database> -f ca.crt, stored in the Secret). See MongoDB and Redis. - Several databases. Add one CronJob per database, each with its own login Secret and its own prefix in the bucket.
What it costs
| Resource | How it is billed |
|---|---|
| Backup pods | Cluster compute units while the Job runs, usually a few minutes a night. Each container counts the larger of its request and its use |
Bucket acme-backups | Per GiB stored per month, averaged over the month. The lifecycle rule keeps about 30 dumps; replaced versions add a little for 7 days. Requests and traffic are free |
Database orders-db-restore | Its plan's monthly price, prorated, for as long as it exists. Delete it after the restore test |
Cluster production | Its own compute, if you created it for this guide |
See Pricing and Bucket usage and billing.
Clean up
# The restore test database
cloider db delete orders-db-restore --yes --wait
# The CronJob, its Jobs and Secrets
kubectl delete namespace backups
# The bucket key, then the bucket with every backup in it
cloider bucket key delete backup-cronjob --yes
cloider bucket delete acme-backups --yes --wait
# What you created only for this guide
cloider db trusted-sources orders-db --remove cluster:production
cloider cluster delete production --yes --wait
cloider db delete orders-db --yes --wait
Deleting the bucket deletes every dump in it. Download the ones you want to keep first.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
The dump container logs connection to server ... timed out | The database doesn't trust the cluster, or PGHOST is the public host. Run part 2 and check the Secret |
server version: 18.0; pg_dump version: 17... and aborting because of server version mismatch | The image's pg_dump is older than the server. Use postgres:18-alpine |
password authentication failed | The password in the Secret is wrong. Recreate orders-db-login |
mysqldump: Got error: 1045: Access denied | Wrong user or password in shop-db-login |
mysqldump: Error: 'Access denied; you need (at least one of) the PROCESS privilege(s)' | --no-tablespaces is missing |
The upload container logs AccessDenied | The key doesn't reach acme-backups, or a bucket policy denies it. Check cloider bucket key list |
The upload container logs NoSuchBucket | The bucket name in the copy destination is wrong |
The pod is OOMKilled or evicted during a large dump | Raise the dump container's memory request. For dumps of many GiB, mount a PersistentVolumeClaim instead of emptyDir, see Persistent volumes |
pg_restore: error: unsupported version (1.16) in file header | Your local pg_restore is older than the dump. Install the PostgreSQL 18 client, or restore inside the cluster |
| The CronJob never runs | kubectl describe cronjob orders-db-backup -n backups shows events. A paused cluster runs no Jobs |
More in Database troubleshooting and Kubernetes troubleshooting.
Next steps
- Backups and restore: Cloider's own backups and point-in-time restores.
- Data protection: make the backup bucket safe from leaked keys.
- Lifecycle rules: tiered retention per prefix.
- Migrate to Cloider: the same dump and restore tools for moving databases.
Frequently asked questions
Does Cloider back up my database automatically?
Yes. Every managed database gets a full backup every 14 days, and every change in between is recorded, so any second of the last 14 days can be restored. You restore into a new database from the Backups tab or with cloider db restore. The backups can't be downloaded and are deleted with the database, which is why you may want your own dumps as well.
How do I run pg_dump on a schedule on Cloider?
Create a Kubernetes CronJob on a Cloider cluster with a postgres image whose version matches the server, give it the database's private host, port, user, password and database name through a Secret, and add the cluster to the database's trusted sources.
How do I upload a database dump to a Cloider bucket?
Use any S3 tool with a bucket access key, the endpoint https://s3.eu-central-2.cloider.com and the region eu-central-2. This guide uses rclone, configured with RCLONE_CONFIG_CLOIDER_* environment variables, to copy the dump into the bucket.
How long are my own backups kept?
As long as you decide. A lifecycle rule on the bucket deletes objects under a prefix a number of days after they were written, for example 30. Without a rule, dumps are kept until you delete them, and billed by the GiB stored.
How do I restore a pg_dump backup into a Cloider database?
Download the dump from the bucket, then run pg_restore --no-owner --no-privileges against the database's public host with its user and password, with your IP in its trusted sources. For large dumps, run pg_restore in a Job inside a Cloider cluster instead.
