Tech Digest

Guide

Should self-hosted apps share one Postgres container or each run their own?

Run one Postgres per app by default. The apps' own compose files pin three different major versions, and a shared server can only move when the strictest app allows it. Share only between apps whose major-version needs are identical.

Last reviewed

Should self-hosted apps share one Postgres container?

No, not by default. Give each app its own Postgres container. The apps' reference compose files ship different majors (Immich on 14, authentik on 16, Paperless-ngx, Outline, Vikunja and Mattermost on 18), authentik supports only 14 to 18, Immich needs VectorChord loaded through shared_preload_libraries and a superuser, and Synapse needs a database with C collation. One shared server can only upgrade when every app on it allows it, and a major upgrade dumps or migrates every database at once. Share an instance only between apps whose supported majors are identical and that need no extension.

Give each app its own Postgres container. That is the right default for a home server, and the reason is not purity: the apps themselves disagree about which Postgres they want. Immich's reference compose file runs Postgres 14, authentik's runs 16, and Paperless-ngx, Outline, Vikunja and Mattermost all run 18. One shared server has to sit inside every app's supported range at once, and it can only upgrade when the strictest app on it lets it. Share a server only between apps whose major-version needs are identical and that need nothing beyond plain Postgres.

The case for one shared Postgres, stated fairly#

The best-argued version of the other answer comes from a homelab write-up that moved six Postgres containers (Home Assistant, Wiki.js, Immich, a monitoring stack and two side projects) onto one dedicated VM. The author reports memory dropping "from 3.2GB across six containers to 1.8GB with one consolidated instance": 1.4 GB saved, or about 230 MB for each container removed. Add one backup job instead of six, one place to tune, one set of credentials to rotate.

That is a real saving, and on a 4 GB mini PC it is the difference between running Immich and not. The same post is also honest about the costs. It says consolidation suits low-to-moderate traffic, that Immich's indexing can cause contention, that isolation decreases, and that apps requiring different major versions cannot share one instance.

That last caveat is the one the whole decision turns on, and the post does not check it against real apps. The table below does.

What each app actually requires from Postgres#

Every row comes from the project's own documentation, its reference compose file, or its source code, read on 9 October 2026. "Ships" is the Postgres image in the compose file the project tells you to start from.

AppSupported PostgresShipsExtra requirementsCan avoid Postgres
Immich 3.3.114 or later, earlier than 20ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0VectorChord 0.3 to before 2.0, shared_preload_libraries = 'vchord.so', superuser by defaultNo
authentik 2026.8.314 to 18postgres:16-alpinePassword under 100 charactersNo
Nextcloud 3514, 15, 16, 17, 18 (18 recommended)No Postgres image pinnedNone stated beyond the version listYes, MariaDB
Paperless-ngx14 or later (Django 5.2's floor)postgres:18NoneYes, SQLite or MariaDB
Outline14 or laterpostgres:18Redis 4 or laterNo
Mattermost14 or later; 15 or later from v12.0 (October 2026)postgres:18-alpineNoneNo, MySQL is deprecated from v11
Synapse14 or later, refuses to start below; follows Postgres end of lifeNo Postgres image pinnedDatabase in UTF8 with C collation and ctypeYes, SQLite
VikunjaNo floor statedpostgres:18Optional ParadeDB image for full-text searchYes, SQLite or MySQL

Read across the "Ships" column and the problem is plain. Three different majors, in the files people actually copy. Read down "Supported" and the shared window today is 14 to 18: authentik closes the top at 18 and nearly everyone opens the bottom at 14.

That window is about to shrink. Postgres 14 reaches end of life on 12 November 2026. Mattermost's documentation already schedules 15 as the minimum from v12.0, and Synapse's deprecation policy is to withdraw support for a Postgres version once upstream ends it. So within weeks, a server shared by these eight apps must be on 15 to 18, and Immich's own image, the one its compose file pulls, is on 14.

Why the strictest app sets the pace for every app#

A Postgres major upgrade is not a container restart. The pinned major in your compose file is there because a Postgres container that finds a data directory from an older major refuses to start. Postgres's own documentation gives the two ways across: dump and restore with pg_dumpall, or migrate in place with pg_upgrade. Both work on the whole cluster, every database at once.

On separate containers, that means one app, one maintenance window, one dump. When Mattermost 12 wants Postgres 15, you upgrade Mattermost's database and nothing else notices.

On a shared server, every upgrade is everyone's upgrade:

  • The ceiling is the most conservative app. The day you want Postgres 19 for one app, authentik's documented 14 to 18 says no, and nothing on that server moves until authentik's documentation changes.
  • The floor is the most demanding app. When Mattermost requires 15, the whole server has to leave 14 first, which means taking Immich, authentik and Synapse through a major upgrade they did not ask for, on Mattermost's schedule.
  • The downtime is shared. The dump and restore takes every app offline together, and a failure in one database's restore holds the others hostage while you work out which one broke.

That coupling is what you pay for the 230 MB per container. For an app you rarely touch, it can sit there for years. For a stack where one app has a fast release cycle, it is a scheduled outage for all of them.

Immich is the app that makes sharing expensive#

Most rows in that table are plain Postgres with a version range. Immich is not, and three of its requirements land on everyone else sharing its server.

The extension is server-wide. Immich requires shared_preload_libraries = 'vchord.so'. Postgres documents that this parameter "can only be set at server start", and that if a listed library is not found, the server fails to start. So the shared server must run an image that contains VectorChord, either Immich's own or one you build, and every other app's database now lives inside an Immich-shaped Postgres. Its version range also moves on Immich's schedule: VectorChord has to stay between 0.3 and 2.0.

The role is a superuser. Immich's documentation says it "expects superuser permission on the Postgres database", and running without it is described as being for advanced users only. Postgres's documentation is direct about what that grants: a superuser "bypasses all permission checks, except the right to log in". On a shared server, Immich's credentials, which sit in an .env file next to its compose file, can read and drop every other app's database, including authentik's, which holds the identity provider every other app logs in through (see Single sign-on for self-hosters).

Its backups. Immich's documentation says automated database backups require superuser because they use pg_dumpall, which would dump every database on the server into Immich's backup folder. The source of release 3.3.1 does something narrower: the automated backup calls pg_dump against Immich's own database only. So on current releases the nightly backup does not sweep up your other apps, but the documentation and the code disagree, and that is a reason to check after every Immich upgrade rather than assume.

None of this is a problem on Immich's own database container, which is why its compose file ships one.

The smaller requirements that bite on a shared server#

Synapse wants a C locale database. Synapse checks that its database has UTF8 encoding and C collation and ctype, and refuses to start otherwise unless you set allow_unsafe_locale. The official Debian-based postgres image sets LANG en_US.utf8, which becomes the default locale of every database it creates, so letting the image create Synapse's database through POSTGRES_DB produces one Synapse rejects. The fix is per database, which means it does not prevent sharing:

bash
docker compose exec -T db createdb -U postgres \
  --encoding=UTF8 --locale=C --template=template0 \
  --owner=synapse_user synapse

authentik limits password length. Its documentation notes that Postgres passwords longer than 99 characters are not supported, so a shared-server password policy has to respect authentik's limit, not just Postgres's.

Outline also needs Redis 4 or later. Consolidating Postgres does not consolidate the Redis containers, so the memory saving is smaller than the container count suggests.

When sharing is the right call#

Share a Postgres server when every app on it meets three conditions:

  1. The same supported majors, today and in the next release. On current versions, Paperless-ngx, Outline and Vikunja all pin postgres:18 and none states a ceiling below it. They can share, and so can Nextcloud 35 if you run it on Postgres rather than MariaDB.
  2. No preload extension and no superuser. Plain Postgres, one ordinary role per database, owning only that database.
  3. You would upgrade them together anyway. Apps you update in the same monthly window lose nothing when their database upgrade happens in that window too.

Immich fails conditions 2 and 1 (its image is on 14). authentik fails condition 1 for anyone who wants to move past 18. Mattermost fails condition 3 for anyone tracking its feature releases rather than its ESR. They stay on their own containers.

A shared server also needs one role per app, never one role for all of them:

sql
CREATE ROLE outline LOGIN PASSWORD 'change-me';
CREATE DATABASE outline OWNER outline;
REVOKE CONNECT ON DATABASE outline FROM PUBLIC;

Running one per app without the cost#

Most of the overhead people object to is typing, not memory. A per-app database is a service in each app's own compose file, pinned to the major that app ships, on the app's internal network with no published port. Docker Compose conventions covers the layout, including the healthcheck that depends_on: condition: service_healthy waits on.

Backups do not need one script per app either. A loop over your compose projects dumps each database consistently, and Backing up a running database has the exact pg_dump commands and the reason a copy of the data directory is not a backup. Per-app dumps also restore one app without touching the others, which is the restore you will actually need.

Moving a service later is simpler too, and so is rehearsing a restore. A per-app database travels with its compose project; a database on a shared server has to be extracted, and Moving a service to a new machine covers why the target Postgres must be the same major or newer.

When this answer stops applying#

  • Memory is the binding constraint. On a 2 GB or 4 GB machine, 230 MB per container can decide whether a stack fits. Consolidate the plain-Postgres apps that agree, and keep Immich separate.
  • You run a managed Postgres. A database service with its own upgrade tooling, replicas and per-database roles changes the cost of the coupling. That is not a home server.
  • The table moves. Every row reflects the versions named, checked on 9 October 2026. authentik adding Postgres 19, or Immich shipping a newer major in its image, changes which apps can share.

What to do next#

Open each compose file on your server and write down the Postgres major it pins. If they all match and none is Immich, a shared server is a reasonable saving. If they do not, keep them separate and pin each to the major its project ships. Then make sure every one of them is in a nightly dump with Backup planner, and check Mattermost and Synapse before Postgres 14 reaches end of life on 12 November 2026.

Questions#

How much memory does one shared Postgres save?

One published consolidation went from 3.2 GB across six Postgres containers to 1.8 GB on one instance, a saving of 1.4 GB, or roughly 230 MB per container removed. That is a real number on a 4 GB machine and a small one on a 32 GB machine. The same author lists the costs in the same post: apps needing different major versions cannot share, isolation decreases, and one heavy workload such as Immich indexing affects the others.

Can Immich use an existing Postgres server?

Yes, if that server meets Immich's terms: Postgres 14 or later and earlier than 20, the VectorChord extension between 0.3 and 2.0, shared_preload_libraries = 'vchord.so' in the server configuration, and, by default, a superuser role for Immich. The preload line is server-wide and needs a restart, and the server refuses to start if the library is missing, so the shared server has to run an image that contains VectorChord. Most self-hosters are better off keeping Immich's own database container.

Why does Synapse complain about the database collation?

Synapse checks that its database uses UTF8 encoding with collation and ctype set to C, and refuses to start otherwise unless you set allow_unsafe_locale. The official Debian-based postgres image sets LANG en_US.utf8, so a database created by the image's POSTGRES_DB variable fails the check. Create Synapse's database yourself with createdb --encoding=UTF8 --locale=C --template=template0. This is a per-database setting, so it does not stop Synapse sharing a server.

What Postgres version should I use for a new self-hosted app in 2026?

Whatever the app's own compose file pins, which for most current projects is 18. Paperless-ngx, Outline, Vikunja and Mattermost's Docker repository all pin 18; Nextcloud 35 recommends 18. The exceptions matter: Immich's bundled image is still Postgres 14, which reaches end of life on 12 November 2026, and authentik ships 16 and supports up to 18. Do not pick a major the project does not list.

Does a shared Postgres make backups simpler?

It makes them one command instead of several, and that is the whole gain. A pg_dumpall of a shared server captures every app's database in one file, which also means restoring one app from it means extracting that app's part. Per-app containers give you one dump per app, restorable on their own, which is what you want when only one app broke. A loop over your compose projects removes the extra typing.

Is one Postgres per app wasteful on a Raspberry Pi?

It costs memory, and on a 4 GB board that can matter. If you are short of memory, consolidate the apps that agree with each other: on current releases Paperless-ngx, Outline and Vikunja all pin Postgres 18 and need no extension, so they can share one server with little coupling. Keep Immich on its own container whatever the hardware, because its extension and superuser requirement are what make sharing expensive.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.