docker-pg-backup: scheduled Postgres dumps from a container
A cron job that will back up databases running in a docker postgres container
At a glance
- What is it?
- kartoza/docker-pg-backup is a container that runs PostgreSQL and PostGIS backups on a cron schedule, writes them into an ordered year and month directory tree, and can push them to S3 with s3cmd. Its restore path is the more interesting half: it refuses to restore into a non-empty database and can pick a specific backup by date and time.
- Who is it for?
- Adopt docker-pg-backup if you run Postgres in Docker and want scheduled dumps that land in a browsable year and month tree, can go to S3, and can be restored by date and time without a manual rummage through files.
- Can I use it commercially?
- Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 59 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A backup container that assumes you are already running Postgres in Docker
The project is described as a cron job that backs up databases running in a Docker Postgres container, and the overview expands that into a container running PostgreSQL and PostGIS backups, noting that PostGIS is not required because it will back up any Postgres database. It is primarily intended to be used with the kartoza/postgis image.
The default behaviour is a backup once per night at 23h00, written into a directory ordered by year and month, and the schedule is adjustable through the CRON_SCHEDULE environment variable, for example for hourly or every thirty minutes. Backup and restore work to a file or to S3 environments, tested with MinIO.
The image is versioned to match the database image you are running, and the README strongly suggests using a tagged image that matches the PostgreSQL image you are running, giving kartoza/pg-backup:18-3.6 for backing up a kartoza/postgis:18-3.6 database. The pull looks like this:
POSTGRES_MAJOR_VERSION=18
POSTGIS_MAJOR_VERSION=3
POSTGIS_MINOR_RELEASE=6
docker pull kartoza/pg-backup:$POSTGRES_MAJOR_VERSION-$POSTGIS_MAJOR_VERSION.${POSTGIS_MINOR_RELEASE}The three variables at the top are the PostgreSQL major version, the PostGIS major version and the PostGIS minor release, and they feed the tag. The reason for tying the backup image to the database image is straightforward: a pg_dump from a different major version than the server is not a supported operation, so the tag is a guard rail rather than a convenience.
The primary language of the repository is Shell, the licence is GPL-2.0, and the last push was on 2026-08-25. The recent release tags are versioned with the upstream version and the build date, v18.3.6 with builds on 2026-08-25, 2026-08-22 and 2026-07-03, so you can pin an exact build. The project is not archived.
The compose file wires the backup container to a healthy database
Running the services is the ordinary Docker Compose path, with an env file and one command. First you copy the example env file, and the example file in the repository is .example.env:
cp .example.env .envThen you start the services:
docker-compose up -d The compose file in the repository shows the relationship between the two containers, and the detail that matters is in the backup service rather than the database one. The database is the kartoza/postgis image with the same version variables, a named volume for the data directory, and a healthcheck that runs pg_isready against the gis database as the docker user. The backup service is the kartoza/pg-backup image with the matching tag, a named volume mounted at /backups, and the connection details as environment variables, DUMPPREFIX set to PG and POSTGRES_HOST set to db.
Two settings in that service are worth understanding before you change them. RUN_AS_ROOT=true is set, and CRON_SCHEDULE is present but commented out, which means the image default of once per night is in effect unless you uncomment it. The same pattern appears with CONSOLE_LOGGING=TRUE, and with DB_DUMP_ENCRYPTION and DB_TABLES both commented out, so encryption of the dump and table-level dumping are opt-in features rather than defaults.
The depends_on block waits for the database to be healthy, with the condition service_healthy, so the backup cron does not start trying to dump a server that is not accepting connections yet. That is a small detail that avoids a class of confusing first-run errors.
Building the image yourself is the same three commands as any clone and build:
git clone https://github.com/kartoza/docker-pg-backup.git
cd docker-pg-backup
./build.sh The build script is at the root of the repository, alongside build-test.sh and a docker-compose.build.yml, and the repository also has a scenario_tests/ directory and a scripts/ directory holding the shell code that gets copied into the image.
Backup filenames encode the date, and the path is the date
The default archive path inside the container is built from the current date, and the year and month are directories while the rest of the timestamp is in the filename:
/backups/$(date +%Y)/$(date +%B)/${DUMPPREFIX}_${DB}.$(date +%d-%B-%Y-%H-%M).dmpWith DUMPPREFIX set to PG and a database named gis, the README gives the concrete example:
/backups/2019/February/PG_gis.17-February-2019-14-30.dmpThe filename includes hour and minute to support sub-daily backups, which is the same reason the directory tree stops at the month: once a year and once a month, the rest of the timestamp has to live in the filename, and adding it to the path would mean a directory per run.
That layout has a practical benefit for restores. A tree of year and month directories can be browsed and selected with ordinary tools, and a path like 2019/February tells you the month without opening anything. It also means the number of directories stays small no matter how often you back up.
If you want a stable name instead, there is an ARCHIVE_FILENAME setting, empty by default, which fixes the filename to that prefix. With ARCHIVE_FILENAME set to latest the result is:
/backups/latest.gis.dmpA fixed name is what you want for the pattern of keeping one known-good copy, and it is what you would use with an external retention policy. It is also the wrong choice for a history, because each run overwrites the previous one.
Dump format, S3 upload and the config mounts
The dump format is set with DUMP_ARGS, and the image defaults to one of two documented forms. The custom format uses DUMP_ARGS=-Fc, which dumps a compressed archive of the database. The directory format uses DUMP_ARGS=-Fd, which dumps the database into a directory format. For S3 backends the directory format is compressed into a tar archive, because S3 stores objects rather than trees.
That difference is the one to plan around. A custom format archive is a single file, which is simple to copy around and to checksum. A directory dump is faster to restore in some cases and is what the tooling expects for certain Postgres features, but it has to be tarred for upload.
The S3 path uses s3cmd, and there is a ready-made docker-compose-s3.yml for a quick start. The image also supports mounting configuration, and the README lists three mount points. There is s3cfg for backing up to an S3 backend, which you mount like this:
-v /data:/settingsThere is backup-cron for any custom configuration you need to specify in the file, and there is backup_monitoring.sh for any custom monitoring state on database dump completion or failure. That last one is a hook for wiring the result of a backup into whatever you already use for alerting, and the fact that it is a documented mount point rather than a setting suggests it is meant to be replaced with your own script.
The Dockerfile shows how the image is assembled. It starts from the kartoza/postgis base image, installs cron, python3-pip, vim, gettext and jq, installs s3cmd and python-magic with pip, creates a log file for cron, adds the scripts directory as /backup-scripts with the shell files made executable, and writes a build info file recording the Postgres major version and the base image digest. The entrypoint is bash with the start script, and a testing stage extends the production stage with the scenario test requirements.
Restore refuses to overwrite, and can pick a backup by date
The restore path has more thought in it than the backup path, and the first thing it does is refuse to do something dangerous. The restore script will exit if you try to restore into an existing TARGET_DB, and the README calls it generally a good practice to restore into an empty new database and then manually drop and rename the databases.
That guard is the whole design in one line. A restore that overwrites a live database has no undo, so the script makes you create an empty one and then swap it in yourself, which is a rename rather than a restore, and renaming is fast.
The variables that drive it are straightforward. TARGET_DB is the name of the database to restore into, TARGET_ARCHIVE is the full path of the archive to restore, and STORAGE_BACKEND determines where the archive is fetched from, whether it is downloaded and processed for S3 backends or read from local file backups. WITH_POSTGIS is Kartoza specific and generates the PostGIS extension along with the restore process. When the backend is S3 the files are downloaded, either .gz or .dir.tar.gz, locally, and then the restore happens into an empty database.
There is also a date-based restore, which is the feature that makes the year and month directory tree worth having. TARGET_ARCHIVE_DATE takes a date only, for example "2023-03-24", and restores the latest backup of that day. TARGET_ARCHIVE_DATETIME takes a date and time, for example "2023-03-24-14-30", and restores the backup from that moment on that day. The minute granularity in the backup filename is what makes the second form work.
With the environment set in the compose file, the restore is one command:
docker-compose exec dbbackups /backup-scripts/restore.shThe full environment variable reference lives in docker-env.md in the repository, and the README notes that the entrypoint supports several values, with backup being the default and running the backup script either as a cron job or as a one-off.
How the images are published, and the one thing you have to edit
The GitHub Actions produce three images: kartoza/pg-backup:latest, the version-matched tag, and a third tag that appends the UTC date, in the form of the version variables followed by a date stamp. So for a given Postgres and PostGIS combination there is a rolling latest, a stable version tag, and a dated build you can pin to.
The dated tag is the useful one for a team that wants to know exactly which image is running, and the version-matched tag is the one the README tells you to use, because a dump taken by a mismatched pg_dump is not a dump you can trust.
There is one documented manual step in the release process, and it is worth knowing about in advance. If there is a bug in the version-matched release image, you need to update ./github/workflows/deploy-image.yaml and set pgbackupPatchMajorVersionBugs to yes to allow the action to update the current image, because otherwise it will only be updated once in the release cycle.
That is a small but real operational detail: a patched image does not replace the existing version tag automatically, so a team that pulls the version tag will keep getting the old image until the workflow is edited. If you pin the version tag, you also need a process for knowing when the pinned image has been superseded.
The repository itself is modest in size: a Dockerfile, the compose files, build.sh and build-test.sh, the scripts/ directory that becomes /backup-scripts in the image, the scenario_tests/ directory, an .example.env, docker-env.md for the variable reference, and the usual GitHub and issue templates.
Editorial conclusion
Adopt docker-pg-backup if you run Postgres in Docker and want scheduled dumps that land in a browsable year and month tree, can go to S3, and can be restored by date and time without a manual rummage through files. Do not adopt it expecting a general purpose backup tool, since it is tied to the kartoza/postgis family by its tagging scheme, and do not point it at a database you cannot rebuild, because the restore path deliberately refuses to overwrite and assumes you will restore into an empty database and rename it into place. Before you commit, pin the image tag to the Postgres version you actually run, read docker-env.md for the full variable list including the DUMP_ARGS format choice, and decide whether you want the custom format archive or the directory format, because S3 uploads force the latter into a tar.
Frequently asked questions
What does the kartoza/docker-pg-backup container do?
It runs PostgreSQL and PostGIS backups on a cron schedule, by default once per night at 23h00, and can be retuned with the CRON_SCHEDULE environment variable for hourly or every thirty minutes. Backups and restores work to a local file or to S3 storage, tested with MinIO.
Which image tag should I use for docker-pg-backup?
A tagged image matching the PostgreSQL image you are running, for example kartoza/pg-backup:18-3.6 for a kartoza/postgis:18-3.6 database. The GitHub Actions also publish a latest tag and a date-stamped tag of the form $POSTGRES_MAJOR_VERSION-$POSTGIS_MAJOR_VERSION.${POSTGIS_MINOR_RELEASE}-$(date -u +%Y.%m.%d).
How do I restore a backup with docker-pg-backup?
Set TARGET_DB, TARGET_ARCHIVE and STORAGE_BACKEND, add WITH_POSTGIS if you need the extension created, and run docker-compose exec dbbackups /backup-scripts/restore.sh. The script exits if you try to restore into an existing database, so restore into an empty one and then drop and rename the databases yourself.
Can docker-pg-backup restore a specific point in time?
Yes. TARGET_ARCHIVE_DATE takes a date such as 2023-03-24 and restores the latest backup of that day, while TARGET_ARCHIVE_DATETIME takes a date and time such as 2023-03-24-14-30 and restores that exact backup, which is why the default filename includes hours and minutes.
How do I store backups in S3 with docker-pg-backup?
The image uses s3cmd for uploads, and a quick start compose file, docker-compose-s3.yml, is provided. You mount an s3cfg file by passing a volume such as -v /data:/settings, and the directory dump format is compressed into a tar archive for S3 backends.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kartoza-docker-pg-backup)Was this useful?