From 3e9b4eaaebf00d7a8ece67f02e2d6546402f4de7 Mon Sep 17 00:00:00 2001
From: Tianon Gravi <admwiggin@gmail.com>
Date: Mon, 3 Jun 2024 13:57:56 -0700
Subject: [PATCH] Replace `su-exec` with `gosu`

There's a major issue with `su-exec` whose fix has gone unreleased for 5 years (typos leading to running code as root, the opposite of the purpose of the program).

This also decreases our Debian vs Alpine variance.

Due to user scripts/downstream code potentially using `su-exec`, I have included a compatibility symlink to `su-exec` for all versions less than the 17 pre-release.
---
 12/alpine3.19/Dockerfile              | 32 +++++++++++++++++++++++--
 12/alpine3.19/docker-ensure-initdb.sh |  2 +-
 12/alpine3.19/docker-entrypoint.sh    |  2 +-
 12/alpine3.20/Dockerfile              | 32 +++++++++++++++++++++++--
 12/alpine3.20/docker-ensure-initdb.sh |  2 +-
 12/alpine3.20/docker-entrypoint.sh    |  2 +-
 13/alpine3.19/Dockerfile              | 32 +++++++++++++++++++++++--
 13/alpine3.19/docker-ensure-initdb.sh |  2 +-
 13/alpine3.19/docker-entrypoint.sh    |  2 +-
 13/alpine3.20/Dockerfile              | 32 +++++++++++++++++++++++--
 13/alpine3.20/docker-ensure-initdb.sh |  2 +-
 13/alpine3.20/docker-entrypoint.sh    |  2 +-
 14/alpine3.19/Dockerfile              | 32 +++++++++++++++++++++++--
 14/alpine3.19/docker-ensure-initdb.sh |  2 +-
 14/alpine3.19/docker-entrypoint.sh    |  2 +-
 14/alpine3.20/Dockerfile              | 32 +++++++++++++++++++++++--
 14/alpine3.20/docker-ensure-initdb.sh |  2 +-
 14/alpine3.20/docker-entrypoint.sh    |  2 +-
 15/alpine3.19/Dockerfile              | 32 +++++++++++++++++++++++--
 15/alpine3.19/docker-ensure-initdb.sh |  2 +-
 15/alpine3.19/docker-entrypoint.sh    |  2 +-
 15/alpine3.20/Dockerfile              | 32 +++++++++++++++++++++++--
 15/alpine3.20/docker-ensure-initdb.sh |  2 +-
 15/alpine3.20/docker-entrypoint.sh    |  2 +-
 16/alpine3.19/Dockerfile              | 32 +++++++++++++++++++++++--
 16/alpine3.19/docker-ensure-initdb.sh |  2 +-
 16/alpine3.19/docker-entrypoint.sh    |  2 +-
 16/alpine3.20/Dockerfile              | 32 +++++++++++++++++++++++--
 16/alpine3.20/docker-ensure-initdb.sh |  2 +-
 16/alpine3.20/docker-entrypoint.sh    |  2 +-
 17/alpine3.19/Dockerfile              | 31 ++++++++++++++++++++++--
 17/alpine3.19/docker-ensure-initdb.sh |  2 +-
 17/alpine3.19/docker-entrypoint.sh    |  2 +-
 17/alpine3.20/Dockerfile              | 31 ++++++++++++++++++++++--
 17/alpine3.20/docker-ensure-initdb.sh |  2 +-
 17/alpine3.20/docker-entrypoint.sh    |  2 +-
 Dockerfile-alpine.template            | 34 +++++++++++++++++++++++++--
 apply-templates.sh                    |  5 ++--
 38 files changed, 416 insertions(+), 53 deletions(-)

diff --git a/12/alpine3.19/Dockerfile b/12/alpine3.19/Dockerfile
index ecc8522104..eb46f0fe36 100644
--- a/12/alpine3.19/Dockerfile
+++ b/12/alpine3.19/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -135,7 +164,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/12/alpine3.19/docker-ensure-initdb.sh b/12/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/12/alpine3.19/docker-ensure-initdb.sh
+++ b/12/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/12/alpine3.19/docker-entrypoint.sh b/12/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/12/alpine3.19/docker-entrypoint.sh
+++ b/12/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/12/alpine3.20/Dockerfile b/12/alpine3.20/Dockerfile
index 74d5277523..f1caf318c7 100644
--- a/12/alpine3.20/Dockerfile
+++ b/12/alpine3.20/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -135,7 +164,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/12/alpine3.20/docker-ensure-initdb.sh b/12/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/12/alpine3.20/docker-ensure-initdb.sh
+++ b/12/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/12/alpine3.20/docker-entrypoint.sh b/12/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/12/alpine3.20/docker-entrypoint.sh
+++ b/12/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/13/alpine3.19/Dockerfile b/13/alpine3.19/Dockerfile
index 962b528885..39a23522a4 100644
--- a/13/alpine3.19/Dockerfile
+++ b/13/alpine3.19/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -135,7 +164,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/13/alpine3.19/docker-ensure-initdb.sh b/13/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/13/alpine3.19/docker-ensure-initdb.sh
+++ b/13/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/13/alpine3.19/docker-entrypoint.sh b/13/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/13/alpine3.19/docker-entrypoint.sh
+++ b/13/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/13/alpine3.20/Dockerfile b/13/alpine3.20/Dockerfile
index eb373d2cd9..567da31557 100644
--- a/13/alpine3.20/Dockerfile
+++ b/13/alpine3.20/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -135,7 +164,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/13/alpine3.20/docker-ensure-initdb.sh b/13/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/13/alpine3.20/docker-ensure-initdb.sh
+++ b/13/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/13/alpine3.20/docker-entrypoint.sh b/13/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/13/alpine3.20/docker-entrypoint.sh
+++ b/13/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/14/alpine3.19/Dockerfile b/14/alpine3.19/Dockerfile
index 74f2c53e78..461318e2b8 100644
--- a/14/alpine3.19/Dockerfile
+++ b/14/alpine3.19/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -138,7 +167,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/14/alpine3.19/docker-ensure-initdb.sh b/14/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/14/alpine3.19/docker-ensure-initdb.sh
+++ b/14/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/14/alpine3.19/docker-entrypoint.sh b/14/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/14/alpine3.19/docker-entrypoint.sh
+++ b/14/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/14/alpine3.20/Dockerfile b/14/alpine3.20/Dockerfile
index a577a1f994..dc839d7c32 100644
--- a/14/alpine3.20/Dockerfile
+++ b/14/alpine3.20/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -138,7 +167,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/14/alpine3.20/docker-ensure-initdb.sh b/14/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/14/alpine3.20/docker-ensure-initdb.sh
+++ b/14/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/14/alpine3.20/docker-entrypoint.sh b/14/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/14/alpine3.20/docker-entrypoint.sh
+++ b/14/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/15/alpine3.19/Dockerfile b/15/alpine3.19/Dockerfile
index 0a34e0dc97..2f249aa430 100644
--- a/15/alpine3.19/Dockerfile
+++ b/15/alpine3.19/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -141,7 +170,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/15/alpine3.19/docker-ensure-initdb.sh b/15/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/15/alpine3.19/docker-ensure-initdb.sh
+++ b/15/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/15/alpine3.19/docker-entrypoint.sh b/15/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/15/alpine3.19/docker-entrypoint.sh
+++ b/15/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/15/alpine3.20/Dockerfile b/15/alpine3.20/Dockerfile
index 1fac96c7a4..79b20ac311 100644
--- a/15/alpine3.20/Dockerfile
+++ b/15/alpine3.20/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -141,7 +170,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/15/alpine3.20/docker-ensure-initdb.sh b/15/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/15/alpine3.20/docker-ensure-initdb.sh
+++ b/15/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/15/alpine3.20/docker-entrypoint.sh b/15/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/15/alpine3.20/docker-entrypoint.sh
+++ b/15/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/16/alpine3.19/Dockerfile b/16/alpine3.19/Dockerfile
index 09fb413aea..f949bbb499 100644
--- a/16/alpine3.19/Dockerfile
+++ b/16/alpine3.19/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -140,7 +169,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/16/alpine3.19/docker-ensure-initdb.sh b/16/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/16/alpine3.19/docker-ensure-initdb.sh
+++ b/16/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/16/alpine3.19/docker-entrypoint.sh b/16/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/16/alpine3.19/docker-entrypoint.sh
+++ b/16/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/16/alpine3.20/Dockerfile b/16/alpine3.20/Dockerfile
index 1620037cf1..b7606c5b7a 100644
--- a/16/alpine3.20/Dockerfile
+++ b/16/alpine3.20/Dockerfile
@@ -14,7 +14,36 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -140,7 +169,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/16/alpine3.20/docker-ensure-initdb.sh b/16/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/16/alpine3.20/docker-ensure-initdb.sh
+++ b/16/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/16/alpine3.20/docker-entrypoint.sh b/16/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/16/alpine3.20/docker-entrypoint.sh
+++ b/16/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/17/alpine3.19/Dockerfile b/17/alpine3.19/Dockerfile
index 4d6c3d61fb..14ae82dccb 100644
--- a/17/alpine3.19/Dockerfile
+++ b/17/alpine3.19/Dockerfile
@@ -14,7 +14,35 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -139,7 +167,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/17/alpine3.19/docker-ensure-initdb.sh b/17/alpine3.19/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/17/alpine3.19/docker-ensure-initdb.sh
+++ b/17/alpine3.19/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/17/alpine3.19/docker-entrypoint.sh b/17/alpine3.19/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/17/alpine3.19/docker-entrypoint.sh
+++ b/17/alpine3.19/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/17/alpine3.20/Dockerfile b/17/alpine3.20/Dockerfile
index 39375a0e16..f23096b472 100644
--- a/17/alpine3.20/Dockerfile
+++ b/17/alpine3.20/Dockerfile
@@ -14,7 +14,35 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -139,7 +167,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/17/alpine3.20/docker-ensure-initdb.sh b/17/alpine3.20/docker-ensure-initdb.sh
index 2a9758656e..ae1f6b6b90 100755
--- a/17/alpine3.20/docker-ensure-initdb.sh
+++ b/17/alpine3.20/docker-ensure-initdb.sh
@@ -27,7 +27,7 @@ docker_setup_env
 docker_create_db_directories
 if [ "$(id -u)" = '0' ]; then
 	# then restart script as postgres user
-	exec su-exec postgres "$BASH_SOURCE" "$@"
+	exec gosu postgres "$BASH_SOURCE" "$@"
 fi
 
 # only run initialization on an empty data directory
diff --git a/17/alpine3.20/docker-entrypoint.sh b/17/alpine3.20/docker-entrypoint.sh
index 8163d10401..6f59993e08 100755
--- a/17/alpine3.20/docker-entrypoint.sh
+++ b/17/alpine3.20/docker-entrypoint.sh
@@ -310,7 +310,7 @@ _main() {
 		docker_create_db_directories
 		if [ "$(id -u)" = '0' ]; then
 			# then restart script as postgres user
-			exec su-exec postgres "$BASH_SOURCE" "$@"
+			exec gosu postgres "$BASH_SOURCE" "$@"
 		fi
 
 		# only run initialization on an empty data directory
diff --git a/Dockerfile-alpine.template b/Dockerfile-alpine.template
index 8535b20a10..f80942090c 100644
--- a/Dockerfile-alpine.template
+++ b/Dockerfile-alpine.template
@@ -8,7 +8,38 @@ RUN set -eux; \
 	mkdir -p /var/lib/postgresql; \
 	chown -R postgres:postgres /var/lib/postgresql
 
-# su-exec (gosu-compatible) is installed further down
+# grab gosu for easy step-down from root
+# https://github.com/tianon/gosu/releases
+ENV GOSU_VERSION 1.17
+RUN set -eux; \
+	\
+	apk add --no-cache --virtual .gosu-deps \
+		ca-certificates \
+		dpkg \
+		gnupg \
+	; \
+	\
+	dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
+	wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
+	wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
+	\
+# verify the signature
+	export GNUPGHOME="$(mktemp -d)"; \
+	gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
+	gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
+	gpgconf --kill all; \
+	rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
+	\
+# clean up fetch dependencies
+	apk del --no-network .gosu-deps; \
+	\
+	chmod +x /usr/local/bin/gosu; \
+# verify that the binary works
+	gosu --version; \
+	gosu nobody true
+{{ if [ "12", "13", "14", "15", "16" ] | index(env.version) then ( -}}
+RUN set -eux; ln -svf gosu /usr/local/bin/su-exec; su-exec nobody true # backwards compatibility (removed in PostgreSQL 17+)
+{{ ) else "" end -}}
 
 # make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default
 # alpine doesn't require explicit locale-file generation
@@ -151,7 +182,6 @@ RUN set -eux; \
 	apk add --no-cache --virtual .postgresql-rundeps \
 		$runDeps \
 		bash \
-		su-exec \
 		tzdata \
 		zstd \
 # https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.16.0#ICU_data_split
diff --git a/apply-templates.sh b/apply-templates.sh
index 69b7a01a85..aa2d65c6b0 100755
--- a/apply-templates.sh
+++ b/apply-templates.sh
@@ -47,12 +47,9 @@ for version; do
 
 		echo "processing $dir ..."
 
-		cp -a docker-entrypoint.sh docker-ensure-initdb.sh "$dir/"
-
 		case "$variant" in
 			alpine*)
 				template='Dockerfile-alpine.template'
-				sed -i -e 's/gosu/su-exec/g' "$dir/docker-entrypoint.sh" "$dir/docker-ensure-initdb.sh"
 				;;
 			*)
 				template='Dockerfile-debian.template'
@@ -63,5 +60,7 @@ for version; do
 			generated_warning
 			gawk -f "$jqt" "$template"
 		} > "$dir/Dockerfile"
+
+		cp -a docker-entrypoint.sh docker-ensure-initdb.sh "$dir/"
 	done
 done