# PostgreSQL 18 update for self-hosters

**URL:** https://meta.discourse.org/t/postgresql-18-update-for-self-hosters/406194
**Category:** Announcements
**Created:** [August 3, 2026, 4:36am UTC](https://meta.discourse.org/t/postgresql-18-update-for-self-hosters/406194 "2026-08-03T04:36:42Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![chrisr](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/chrisr/32/246622_2.png) [@chrisr](https://meta.discourse.org/u/chrisr)
#### Post date: [August 3, 2026, 4:36am UTC](https://meta.discourse.org/t/postgresql-18-update-for-self-hosters/406194/1 "2026-08-03T04:36:42Z")

</div>

> ⚠ **WARNING!** The upgrade requires a lot of free disk space (2x database size).

We’ve just landed changes to upgrade our Docker image to PostgreSQL 18. Any site admins rebuilding Discourse from the command line will be upgraded to PostgreSQL 18 from the previous PostgreSQL 15. Note that if you held back from upgrading when the PostgreSQL 15 update [happened back in 2025](https://meta.discourse.org/t/postgresql-15-update/349515), you can skip that upgrade and go straight to PostgreSQL 18.

If you had held back the upgrade previously, change the PostgreSQL template in `app.yml` from `templates/postgres.13.template.yml` to `templates/postgres.template.yml`.

As with any upgrade, it is **strongly advised** to take a backup before doing anything.

### Locale changes

Previously we have seen index corruption occur when glibc was updated, such as during OS upgrades. Since PostgreSQL 17 there is a new [builtin locale provider](https://www.postgresql.org/docs/18/locale.html#LOCALE-PROVIDERS) which is decoupled from the OS’ glibc and therefore stable across versions. As part of this upgrade we are moving to this builtin provider using the C.UTF-8 locale. This locale using code point ordering and we recommend it for the aforementioned stability reasons.

In order to accomplish the locale change we need to first create the new DB cluster using the builtin locale provider and then we dump and restore the DB into this cluster. For this reason the disk space requirements are greater than previous upgrades where we performed in-place upgrades.

## Updating

### [Official Install Guide](https://github.com/discourse/discourse/blob/main/docs/INSTALL-cloud.md) (single container)

On your next rebuild, you will see this message at the end:

```plaintext
-------------------------------------------------------------------------------------
UPGRADE OF POSTGRES COMPLETE

Old 15 database is stored at /shared/postgres_data_old

To complete the upgrade, rebuild again using:

./launcher rebuild app
-------------------------------------------------------------------------------------

```

That means everything went well in the upgrade! You just need to issue a new rebuild to get your site back and running.

### Data Container Install

If you are running a setup with a dedicated data container based in the sample supplied in our discourse\_docker repository, you want to be sure you are shutting down PostgreSQL in a safe and clean way.

Nowadays, we have background jobs running queries spanning several minutes, so shutting down the web container will help the data container be shutdown safely.

```plaintext
./launcher stop web_only
./launcher stop data
./launcher rebuild data
./launcher rebuild data
./launcher rebuild web_only

```

Before issuing the first rebuild to the data container, you can tail the PostgreSQL log to see if it was shutdown properly.

Running a `tail -f shared/standalone/log/var-log/postgres/current` should give you the following log if it was clean:

```plaintext
2025-01-24 09:19:06.437 UTC [37] LOG: received smart shutdown request
2025-01-24 09:19:06.444 UTC [37] LOG: background worker "logical replication launcher" (PID 54) exited with exit code 1
2025-01-24 09:19:06.446 UTC [49] LOG: shutting down
2025-01-24 09:19:06.468 UTC [37] LOG: database system is shut down

```

## Postponing the update

If you need to postpone the update during your next rebuild, you can swap the PostgreSQL template on your app.yml file by changing `"templates/postgres.template.yml"` to `"templates/postgres.15.template.yml"`.

This is not recommended, as some site admins will forget to revert the change afterwards.

## Post update optional tasks

### Optimizing PostgreSQL statistics

After the update, the new PostgreSQL won’t have table statistics on hand. You can generate those using:

```plaintext
docker exec -u postgres app \
	/usr/lib/postgresql/18/bin/vacuumdb -d discourse --analyze-in-stages

```

### Cleaning up old data

For a [standard install](https://meta.discourse.org/t/142537?silent=true), you can delete the old data in PG15 format with the following command:

```plaintext
cd /var/discourse
./launcher cleanup

```

If you have a separate data container, you’ll need to remove the backup copy like this:

```plaintext
rm -fr /var/discourse/shared/data/postgres_data_old/

```

## FAQ

### The source cluster was not shut down cleanly

If you get a upgrade failed with the above message, you can try a simpler approach to get it back into a better state.

Restart the old container with `./launcher start app`. Wait a few minutes until it is back up.

Now shut it down again with `./launcher stop app`. After that tail the logs to see if it was a clean one:

```plaintext
tail -f shared/standalone/log/var-log/postgres/current
2025-01-24 09:19:06.437 UTC [37] LOG: received smart shutdown request
2025-01-24 09:19:06.444 UTC [37] LOG: background worker "logical replication launcher" (PID 54) exited with exit code 1
2025-01-24 09:19:06.446 UTC [49] LOG: shutting down
2025-01-24 09:19:06.468 UTC [37] LOG: database system is shut down

```

If the logs do not indicate that the database is shut down, you can start the old container again, enter with `./launcher enter app`, run these commands and tail the logs again once done.

```plaintext
export SVWAIT=300
sv stop nginx
sv stop unicorn
sv stop postgres
exit

```

If the logs look like above, you can now try to upgrade again using `./launcher rebuild app`.

### lc\_collate values for database “postgres” do not match

This errors happens if you are using non-default locales for your database. It was reported that you need 3 variables for it to succeed. Ensure that the `env: ` section of you app.yml file has the 3 lines:

```yaml
  LC_ALL: en_US.UTF-8
  LANG: en_US.UTF-8
  LANGUAGE: en_US.UTF-8

```

Changing `en_US.UTF-8` to your locale.

### Every rebuild does the upgrade again aka upgrade loop

When this happens your upgrade logs will contain

```plaintext
mv: cannot move '/shared/postgres_data' to '/shared/postgres_data_old/postgres_data': Directory not empty
mv: cannot move '/shared/postgres_data_new' to '/shared/postgres_data/postgres_data_new': Directory not empty

```

This means that there are still files from the last upgrade lingering around. Move those elsewhere before continuing.

### I did skip the PostgreSQL 15 update, what to do now?

You can follow the standard instructions at the top of this guide and they will upgrade from your version to 18 without issues.

---

_[View the full topic](https://meta.discourse.org/t/postgresql-18-update-for-self-hosters/406194)._
