# pgautoupgrade: a Postgres image that upgrades your data directory in place

> pgautoupgrade is a MIT licensed Docker image that detects the PostgreSQL major version in an existing data directory and upgrades it with pg_upgrade and the --link option, then starts the server normally, which is the step the official Postgres image cannot do. It ships Alpine and Debian variants, a one-shot mode for Kubernetes init containers, a Bitnami compatibility layer, and a warning that the upgrade is in place and your backups had better exist.

**pgautoupgrade/docker-pgautoupgrade** — A PostgreSQL Docker container that automatically upgrades your database

- Repository: https://github.com/pgautoupgrade/docker-pgautoupgrade
- Website: https://hub.docker.com/r/pgautoupgrade/pgautoupgrade
- Stars: 1,192 · Forks: 32
- Language: Shell
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/pgautoupgrade-docker-pgautoupgrade

## The upgrade the official image cannot do

pgautoupgrade is a PostgreSQL Docker image to automatically upgrade your database.

The gap it fills is named precisely in the README. The official Docker PostgreSQL image has no ability to handle version upgrades, which leaves people to figure it out manually, with a link to the long-running issue on the docker-library/postgres repository where that problem has been discussed.

That is the whole product. Its whole purpose in life is to automatically detect the version of PostgreSQL used in the existing PostgreSQL data directory, then automatically upgrade it, if needed, to the required version of PostgreSQL using `pg_upgrade` with the `--link` option. After that, the PostgreSQL server starts and runs as per normal, and the old cluster data will be removed.

The `--link` option is the technical heart of it. `pg_upgrade` can either copy the data files to a new cluster or link to the old ones and then delete the originals, and the link mode is dramatically faster on large clusters because it avoids rewriting every file. The trade is that the old cluster is not a usable fallback afterwards, which is exactly why the README's warning is emphatic.

The intended user is an operator with a running Postgres container and a need to move it to a newer major version, typically because the version they are on has reached end of life or because the platform they deploy on has moved on. The project is MIT licensed, the primary language is Shell, and the last push to `main` was on 2026-10-15 with the repository not archived. There are no GitHub releases, since the artefact here is a container image rather than a package.

## The image, the tags, and why the base distribution matters

The image is published on Docker Hub as `pgautoupgrade/pgautoupgrade`, and the tag choice is more subtle than a version number.

To always use the latest version of PostgreSQL, use the tag `latest`:

```
pgautoupgrade/pgautoupgrade:latest
```

But the README immediately flags a trap. The `latest` tag here is based on Alpine Linux, whereas the `latest` tag used by the official Docker Postgres container is based on Debian. So a team that moves from the official image to this one by changing the image name alone also changes the base distribution of the database itself.

The fix is the Debian based images, which the README offers as a response to the observation that upgrading from the official Debian based image to one of the Alpine based images does not always work out well. To use either of those, choose the version of PostgreSQL you would like to upgrade to and then change your docker image to match, for example:

```
pgautoupgrade/pgautoupgrade:18-trixie
```

So the naming convention pairs a PostgreSQL major version with a distribution codename, `18-alpine` and `18-trixie` being the two shapes. The repository reflects that with two Dockerfiles, `Dockerfile.alpine` and `Dockerfile.trixie`, and with Renovate configured through `renovate.json` to keep the base images current.

There is one more source of images to be careful with. The images available in the GitHub Container Registry are for debugging purposes only, and they are built from specific code branches for easier distribution and testing of fixes. In other words, a GHCR tag is a branch build, not a release, and it is not what you want running in production.

## One-shot mode and the Kubernetes init container pattern

Sometimes you want the upgrade and nothing else, and the one-shot mode exists for that. The README links it to a discussion issue and describes the mechanism: add an environment variable called `PGAUTO_ONESHOT`, equal to `yes`, when you run the container.

```
$ docker run --name pgauto -it \
	--mount type=bind,source=/path/to/your/database/directory,target=/var/lib/postgresql/data \
	-e POSTGRES_PASSWORD=password \
	-e PGAUTO_ONESHOT=yes \
	<NAME_OF_THE_PGAUTOUPGRADE_IMAGE>
```

In this mode the container performs the upgrade and then exits instead of starting PostgreSQL. That is the shape Kubernetes wants, and the README gives the manifest for running pgautoupgrade as an initContainer, with an example for upgrading a Bitnami Postgres container:

```yaml
initContainers:
- env:
  - name: PGAUTO_ONESHOT
    value: "yes"
  - name: POSTGRES_DB
    value: XXX
  - name: PGDATA
    value: /bitnami/postgresql/data
  - name: POSTGRES_PASSWORD
    value: password
image: pgautoupgrade/pgautoupgrade:18-trixie
name: upgrade-postgres
securityContext:
  runAsUser: 0
volumeMounts:
  - mountPath: /bitnami/postgresql
    name: YYY
```

Two details in that manifest are worth understanding. The `runAsUser: 0` is there because the container has to run as root in one-shot mode, otherwise the existing file permissions of the Postgres data directory cannot be restored, which is the subject of the next section. And the `POSTGRES_PASSWORD` value does not really matter, as it's never used in one-shot mode, so a placeholder is fine.

The init container pattern is the strongest argument for this image. In a Kubernetes deployment, an init container runs to completion before the main container starts, so the upgrade happens as a well-defined step in the pod lifecycle rather than as something a human does by hand during an outage.

## Reindexing, and the lock warning on older servers

After the migration, the image reindexes all databases by default, and the README says this can take some time if they are large. To skip reindexing, set the environment variable `PGAUTO_REINDEX` to `no`:

```
$ docker run --name pgauto -it \
	--mount type=bind,source=/path/to/your/database/directory,target=/var/lib/postgresql/data \
	-e POSTGRES_PASSWORD=password \
	-e PGAUTO_REINDEX=no \
	<NAME_OF_THE_PGAUTOUPGRADE_IMAGE>
```

The reindex is there because `pg_upgrade` with linked clusters leaves the statistics and the system catalogue indexes in a state that the new server wants to rebuild, and skipping it leaves you with a database that works but is slower than it should be.

The warning attached to it is a real operational constraint. PG v15 and below do not support reindexing system tables in a database concurrently. This means, when we start the indexing operation, database locks are placed which do not allow for any modifications as long as the task is running, and the recommendation is to use PG v16 or later where this is not an issue.

So the choice is not simply whether to reindex. On PostgreSQL 16 and later the reindex is a background operation on a live database, and you can leave it enabled. On 15 and below, enabling it means taking write locks for the duration of the reindex, and on a large database that duration is measured in hours. On those versions, `PGAUTO_REINDEX=no` is the safer default, and the index rebuild becomes a deliberate step you schedule.

The repository carries a dedicated `pgautoupgrade-healthcheck.sh`, and the README tells you to remove any healthchecks of your own, because the update process required the project to implement its own healthcheck and no extra one is needed.

## The Bitnami compatibility layer, and its documented limits

A large part of the README is about upgrading from a Bitnami Postgres image, and the reason is two specific differences in how Bitnami's images are built.

The first is that the Bitnami containers do not persist the `postgresql.conf` and `pg_hba.conf` file in the Postgres data directory. If the project detects that these files are missing, it copies a default version of them into the data directory, and if you request the one-shot mode, these files are removed again at the end of the upgrade process.

The second is the user ID. The official Postgres image, and therefore this one as well, uses `999` as the ID for the postgres user inside the container, while Bitnami uses 1001. During the upgrade process a copy of the data is made, and that copy is assigned to ID `999`. If you request one-shot mode, the original file permissions are restored once the upgrade has completed.

That restoration is the reason for the root requirement, and the README states it in operational terms: the container has to run as `root` if using one-shot mode, otherwise the existing file permissions of your Postgres data directory cannot be restored. You can run the container as user `999`, but then you will have to manually apply the file permissions to your Postgres data directory.

The environment variable mapping follows from the same difference. The container uses the official Postgres variables, but can map certain things: Bitnami's `POSTGRESQL_PASSWORD` becomes `POSTGRES_PASSWORD`, and Bitnami's `POSTGRESQL_DATA_DIR` becomes `PGDATA`, which is set automatically to `/bitnami/postgresql/data` if it is not empty.

And the README is unusually candid about the limits of that support. As of writing that paragraph, on the 5th of October 2025, the project tested upgrading from Bitnami Postgres v9.5, v9.6, v10, v11, v12, v13, v14, v15, v16 and v17 to v17 and v18, using the latest available container version from Bitnami Legacy. Bitnami's script and directory structure could change over time, and the project asks for the exact SHA of the image if you hit a problem, and notes that they do not have any access to Bitnami Secure Images.

## Backups, the v18 mount error, and where this is the wrong tool

The README leads with a warning, and it is the one line to read before anything else: backup your data, because this Docker image does an in-place upgrade of the database data, so if something goes wrong you are expected to already have backups you can restore from.

That is the correct expectation to hold. `pg_upgrade` with `--link` is fast precisely because it does not copy your data, and the old cluster is removed afterwards, so there is no in-image rollback. The image makes the upgrade repeatable, it does not make it reversible.

There is also a documented error case that has nothing to do with the upgrade logic. If you want to upgrade to Postgres v18 or later and do not adjust your volume mounts, you can get a mount propagation error from the container runtime, because the data directory path in the official image changed and a volume created at the old path no longer resolves. The README documents the error text, and the fix is to mount the volume at the new location.

There are three cases where this image is the wrong tool.

The first is a multi-version jump. `pg_upgrade` moves a cluster from one major version to the next, so going from 13 to 18 means running the tool repeatedly, once per major version, and each run is a full container start with its own risk. There is no shortcut through the image.

The second is an environment where you cannot take a backup or cannot run as root. Without a backup the in-place upgrade has no safety net, and without root in one-shot mode the file permissions of your data directory are not restored, which leaves a database that may be unreadable by the postgres user.

The third is a deployment that is not Docker. The project is a container image, and its whole mechanism is mounting a data directory into a container that runs the upgrade and then hands the directory to a Postgres server.

The alternative approach, and the one to weigh against, is doing the upgrade yourself with `pg_upgrade` inside the official image, mounting the old and new data directories, and switching the container's volume path when the new cluster is ready. The difference in approach is that pgautoupgrade packages the whole procedure, including the file permission handling, the Bitnami quirks and the reindex decision, into one image and one environment variable, while the manual route gives you full control over every step and the responsibility for every step. For a one-off migration, the manual route is defensible. For a fleet that upgrades on a schedule, the image is the smaller commitment.

## Conclusion

Adopt pgautoupgrade if you run Postgres in Docker and need to move a data directory from one major version to another without hand-running pg_upgrade, since the official image has no upgrade path and the official forum thread on the subject is a long list of manual procedures. Do not adopt it if you cannot take a backup, since the upgrade is in place and irreversible, or if you need to skip more than one major version at a time, since pg_upgrade works one major version at a time. Verify three things first: that the base of your current image matches the base of the tag you are moving to, because the README notes that upgrading from the official Debian image to an Alpine one does not always work out well, that your healthcheck is removed, since the image implements its own, and that the data directory is a bind mount rather than an anonymous volume, so the upgraded cluster survives the container.

## FAQ

### What does the pgautoupgrade Docker image do?

It detects the PostgreSQL version used in the existing data directory and upgrades it to the required version using `pg_upgrade` with the `--link` option, then starts the server normally and removes the old cluster data. It exists because the official Docker Postgres image has no ability to handle version upgrades.

### How do I run pgautoupgrade in one-shot mode?

Set the environment variable `PGAUTO_ONESHOT` to `yes` when you run the container. The container then performs the upgrade and exits instead of running PostgreSQL afterwards, which is the pattern used for a Kubernetes init container.

### What is the difference between the Alpine and Debian pgautoupgrade images?

The `latest` tag is based on Alpine Linux while the official Docker Postgres `latest` is Debian based, and upgrading from the official Debian image to an Alpine one does not always work out well. Debian based images are available too, for example `pgautoupgrade/pgautoupgrade:18-trixie` against `18-alpine`.

### Can I skip the reindex step in pgautoupgrade?

Yes, by setting `PGAUTO_REINDEX=no`. The default is to reindex all databases after the migration, and the README warns that PG v15 and below cannot reindex system tables concurrently, which places database locks that block modifications for the duration, so PG v16 or later is recommended.

### Does pgautoupgrade support upgrading from a Bitnami Postgres image?

Yes, with adjustments, because Bitnami containers do not persist postgresql.conf and pg_hba.conf in the data directory and use user id 1001 rather than 999. The project tested upgrading from Bitnami v9.5 through v17 as of 2025-10-05, has no access to Bitnami Secure Images, and asks for the exact image SHA when a problem is reported.

## Sources

- [Issues](https://github.com/pgautoupgrade/docker-pgautoupgrade/issues)
- [License: MIT](https://github.com/pgautoupgrade/docker-pgautoupgrade/blob/main/LICENSE)
- [pgautoupgrade/docker-pgautoupgrade on GitHub](https://github.com/pgautoupgrade/docker-pgautoupgrade)
- [Project website](https://hub.docker.com/r/pgautoupgrade/pgautoupgrade)
- [README](https://github.com/pgautoupgrade/docker-pgautoupgrade/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pgautoupgrade-docker-pgautoupgrade
