As the number of self-hosted services grows, so does the number of databases I need to manage. I now have 6 PostgreSQL databases running in my home lab, which I needed to manage and back up.
Up until now, I have already been managing them in a single PgAdmin instance, but all the backups were scheduled separately with separate k8s job manifests. It’s time to end the pain.
Overview
The idea is to manage all PostgreSQL databases in a single PgAdmin instance, and also a single backup job that backs up all databases at once. By sharing a single configuration between these, each time there is a new database, I only have to add the connection information once.
Configuration
To add servers to pgAdmin at startup, I use the following configmap, in the format specified in the pgAdmin documentation. The documentation also includes a way to export existing servers from the GUI, which is how I plan to update this configmap in the future.
It’s also possible to specify password by specifying a file with the password in it in this json, but since the pgAdmin GUI does not support exporting it, I decided to leave it out and enter the password manually in the GUI.
Therefore, the passwords for cronjob is stored in a separate secret.
Passwords are stored in a secret, which is currently only being used by the backup job.
Setup of pgAdmin is pretty straightforward. Just mount the servers.json configmap at /pgadmin4/servers.json, and it will automatically load the servers at startup.
The pgAdmin container does not automatically configure the ownership of the /var/lib/pgadmin, so I added an init container to set the ownership to 5050:, which is the user and group that pgAdmin runs as.
apiVersion:batch/v1kind:CronJobmetadata:name:pg-backupnamespace:adminspec:schedule:"0 3 * * *"timeZone:"America/Denver"jobTemplate:spec:template:spec:restartPolicy:OnFailurevolumes:- name:pg-configconfigMap:name:pg-servers- name:pg-credssecret:secretName:db-credentials- name:sharedemptyDir:{}- name:backup-pvcpersistentVolumeClaim:claimName:pg-backupinitContainers:- name:prepare-pgpassimage:linuxserver/yq:3.4.3command:- /bin/bash- -c- | mkdir -p /shared/.pgpass
chmod 700 /shared/.pgpass
jq -r '
.Servers | to_entries[] |
[.value.Name, .value.Host, .value.Port, .value.MaintenanceDB, .value.Username] |
@tsv
' /config/dbs.json | while IFS=$'\t' read -r name host port db user; do
pass=$(cat /secrets/$name)
echo "$host:$port:$db:$user:$pass"
done > /shared/.pgpass/pgpass
chmod 600 /shared/.pgpass/pgpassvolumeMounts:- name:pg-configmountPath:/config- name:pg-credsmountPath:/secrets- name:sharedmountPath:/sharedcontainers:- name:backupimage:postgres:17command:- /bin/bash- -c- | export PGPASSFILE=/shared/.pgpass/pgpass
mkdir -p /backups
while IFS=':' read -r host port db user pass; do
filename="/backups/${host//./_}-${db}-$(date +%F).sql"
echo "Backing up $db@$host:$port as $user → $filename"
PGPASSWORD="$pass" pg_dump -h "$host" -p "$port" -U "$user" -w $db > "$filename"
done < "$PGPASSFILE"volumeMounts:- name:sharedmountPath:/shared- name:backup-pvcmountPath:/backups
Backup file examples:
1
2
-rw-r--r-- 1 root root 570 Jun 5 03:00 comma-db_data_svc_cluster_local-postgres-2025-06-05.sql
-rw-r--r-- 1 root root 570 Jun 5 03:00 linkwarden-db_data_svc_cluster_local-postgres-2025-06-05.sql
Conclusion
I think this setup has made my mental burden of adding a new database much lighter. I do still need to specify the connection information in application manifests and pgAdmin, but the sense of “all the DBs visible in pgAdmin are backed up” is a great relief.