From 23d96f0393e0f11bdf4b351f81e49b68dfe57139 Mon Sep 17 00:00:00 2001 From: Alexander Todorov Date: Fri, 28 Aug 2026 10:28:47 +0300 Subject: [PATCH 1/2] Perform initial wal sync if replication is configured checks for EVNs: - POSTGRES_REPLICATION_USER - POSTGRES_REPLICATION_PASSWORD - POSTGRES_PRIMARY_HOST --- .github/workflows/testing.yml | 4 ++-- 17/Dockerfile | 9 +++++++++ 17/entrypoint.sh | 34 ++++++++++++++++++++++++++++++++ 18/Dockerfile | 4 ++++ 18/entrypoint.sh | 34 ++++++++++++++++++++++++++++++++ docker-compose.yml | 7 +++++++ initdb.d/01_replication_users.sh | 23 +++++++++++++++++++++ testing/test_docker.sh | 24 ++++++++++++++++++++++ 8 files changed, 137 insertions(+), 2 deletions(-) create mode 100755 17/entrypoint.sh create mode 100755 18/entrypoint.sh create mode 100755 initdb.d/01_replication_users.sh diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 76fa9ce..3f7c03a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -12,7 +12,7 @@ concurrency: permissions: read-all jobs: - docker: + docker-build: runs-on: ubuntu-26.04 strategy: matrix: @@ -34,7 +34,7 @@ jobs: docker images - test: + end-to-end: runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 diff --git a/17/Dockerfile b/17/Dockerfile index 71ac39c..b45f15a 100644 --- a/17/Dockerfile +++ b/17/Dockerfile @@ -1,3 +1,12 @@ +# Copyright (c) 2026 Alexander Todorov +# +# Licensed under GNU Affero General Public License v3 or later (AGPLv3+) +# https://www.gnu.org/licenses/agpl-3.0.html + FROM postgres:17@sha256:e38411452a464af89e5adadb8d223bf53b898d47d6ef918b2d58c08707350449 COPY ./docker-entrypoint-initdb.d/00_enable_ssl.sh /docker-entrypoint-initdb.d/ + +COPY entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["entrypoint.sh"] +CMD ["postgres"] diff --git a/17/entrypoint.sh b/17/entrypoint.sh new file mode 100755 index 0000000..55622d9 --- /dev/null +++ b/17/entrypoint.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Copyright (c) 2026 Alexander Todorov +# +# Licensed under GNU Affero General Public License v3 or later (AGPLv3+) +# https://www.gnu.org/licenses/agpl-3.0.html + +echo "INFO: check if replication is configured" +if [ ! -s "$PGDATA/PG_VERSION" ] && [ -n "$POSTGRES_REPLICATION_USER" ]; then + mkdir -p "$PGDATA" + chmod 00700 "$PGDATA" || : + + if [ -z "$POSTGRES_REPLICATION_PASSWORD" ]; then + echo "ERROR: POSTGRES_REPLICATION_PASSWORD is undefined" + exit 1 + fi + + if [ -z "$POSTGRES_PRIMARY_HOST" ]; then + echo "ERROR: POSTGRES_PRIMARY_HOST is undefined" + exit 2 + fi + + echo "INFO: starting initial wal sync" + pg_basebackup \ + --dbname "postgres://$POSTGRES_REPLICATION_USER:$POSTGRES_REPLICATION_PASSWORD@$POSTGRES_PRIMARY_HOST/postgres?sslmode=require" \ + --pgdata "$PGDATA" \ + --progress --verbose --write-recovery-conf --wal-method stream \ + --create-slot --slot "$POSTGRES_REPLICATION_USER" + + echo "INFO: completed initial wal sync" +fi + +echo "INFO: starting postgres" +docker-entrypoint.sh "$@" diff --git a/18/Dockerfile b/18/Dockerfile index b50907f..f5bdee8 100644 --- a/18/Dockerfile +++ b/18/Dockerfile @@ -1,3 +1,7 @@ FROM postgres:18@sha256:06cad38a5d9f5d24b4d83d86def30795d5e4b757fedbf5281172b576dedcd941 COPY ./docker-entrypoint-initdb.d/00_enable_ssl.sh /docker-entrypoint-initdb.d/ + +COPY entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["entrypoint.sh"] +CMD ["postgres"] diff --git a/18/entrypoint.sh b/18/entrypoint.sh new file mode 100755 index 0000000..55622d9 --- /dev/null +++ b/18/entrypoint.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Copyright (c) 2026 Alexander Todorov +# +# Licensed under GNU Affero General Public License v3 or later (AGPLv3+) +# https://www.gnu.org/licenses/agpl-3.0.html + +echo "INFO: check if replication is configured" +if [ ! -s "$PGDATA/PG_VERSION" ] && [ -n "$POSTGRES_REPLICATION_USER" ]; then + mkdir -p "$PGDATA" + chmod 00700 "$PGDATA" || : + + if [ -z "$POSTGRES_REPLICATION_PASSWORD" ]; then + echo "ERROR: POSTGRES_REPLICATION_PASSWORD is undefined" + exit 1 + fi + + if [ -z "$POSTGRES_PRIMARY_HOST" ]; then + echo "ERROR: POSTGRES_PRIMARY_HOST is undefined" + exit 2 + fi + + echo "INFO: starting initial wal sync" + pg_basebackup \ + --dbname "postgres://$POSTGRES_REPLICATION_USER:$POSTGRES_REPLICATION_PASSWORD@$POSTGRES_PRIMARY_HOST/postgres?sslmode=require" \ + --pgdata "$PGDATA" \ + --progress --verbose --write-recovery-conf --wal-method stream \ + --create-slot --slot "$POSTGRES_REPLICATION_USER" + + echo "INFO: completed initial wal sync" +fi + +echo "INFO: starting postgres" +docker-entrypoint.sh "$@" diff --git a/docker-compose.yml b/docker-compose.yml index 0ecf5e9..b927bf4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: restart: always volumes: - db17_data:/var/lib/postgresql + - ./initdb.d/01_replication_users.sh:/docker-entrypoint-initdb.d/01_replication_users.sh:Z environment: POSTGRES_DB: kiwitcms POSTGRES_USER: kiwitcms @@ -73,6 +74,7 @@ services: dockerfile: Dockerfile volumes: - db18_data:/var/lib/postgresql + - ./initdb.d/01_replication_users.sh:/docker-entrypoint-initdb.d/01_replication_users.sh:Z web: container_name: web @@ -99,3 +101,8 @@ services: volumes: db17_data: db18_data: + + +networks: + default: + name: postgres_default diff --git a/initdb.d/01_replication_users.sh b/initdb.d/01_replication_users.sh new file mode 100755 index 0000000..bafd14d --- /dev/null +++ b/initdb.d/01_replication_users.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Copyright (c) 2026 Alexander Todorov +# +# Licensed under GNU Affero General Public License v3 or later (AGPLv3+) +# https://www.gnu.org/licenses/agpl-3.0.html + +set -eu + +replication_user() { + local username=$1 + local password=$2 + + psql --set ON_ERROR_STOP=1 \ + --set=username="$username" \ + --set=password="$password" \ + --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER :"username" WITH ENCRYPTED PASSWORD :'password' REPLICATION; +EOSQL +} + +replication_user "rpl_usr_17" "replicate-me" +replication_user "rpl_usr_18" "replicate-me" diff --git a/testing/test_docker.sh b/testing/test_docker.sh index d4afb61..6db884e 100755 --- a/testing/test_docker.sh +++ b/testing/test_docker.sh @@ -37,6 +37,16 @@ rlJournalStart rlAssertGrep "pg_hba.conf rejects connection .* no encryption" "$rlRun_LOG" rlPhaseEnd + rlPhaseStartTest "Start replication containers" + rlRun -t -c "docker run -d --name=replica_17 --network=postgres_default -e POSTGRES_REPLICATION_USER=rpl_usr_17 -e POSTGRES_REPLICATION_PASSWORD=replicate-me -e POSTGRES_PRIMARY_HOST=postgres_17 postgres-postgres_17:latest" + sleep 120 + rlRun -t -c "docker logs replica_17" + + rlRun -t -c "docker run -d --name=replica_18 --network=postgres_default -e POSTGRES_REPLICATION_USER=rpl_usr_18 -e POSTGRES_REPLICATION_PASSWORD=replicate-me -e POSTGRES_PRIMARY_HOST=postgres_18 postgres-postgres_18:latest" + sleep 120 + rlRun -t -c "docker logs replica_18" + rlPhaseEnd + rlPhaseStartTest "Container restart" rlRun -t -c "docker compose restart" assert_up_and_running @@ -56,7 +66,21 @@ rlJournalStart assert_up_and_running rlPhaseEnd + rlPhaseStartTest "Check content in replicated databases" + REPLICA_17_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' replica_17) + rlRun -t -c "psql --dbname 'postgres://kiwitcms:kiwitcms@$REPLICA_17_ADDRESS/kiwitcms?sslmode=require' -c 'SELECT * FROM management_priority;' | grep '5 rows'" + + REPLICA_18_ADDRESS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' replica_18) + rlRun -t -c "psql --dbname 'postgres://kiwitcms:kiwitcms@$REPLICA_18_ADDRESS/kiwitcms?sslmode=require' -c 'SELECT * FROM management_priority;' | grep '5 rows'" + rlPhaseEnd + rlPhaseStartCleanup + rlRun -t -c "docker kill replica_17" + rlRun -t -c "docker rm replica_17" + + rlRun -t -c "docker kill replica_18" + rlRun -t -c "docker rm replica_18" + rlRun -t -c "docker compose down" if [ -n "$ImageOS" ]; then rlRun -t -c "docker volume rm postgres_db17_data" From 6b95d40a7114cb20b8aa42a5800e194754851dcc Mon Sep 17 00:00:00 2001 From: Alexander Todorov Date: Fri, 28 Aug 2026 11:49:48 +0000 Subject: [PATCH 2/2] Use exec for docker-entrypoint.sh to properly handle signals --- 17/entrypoint.sh | 2 +- 18/entrypoint.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/17/entrypoint.sh b/17/entrypoint.sh index 55622d9..0479f07 100755 --- a/17/entrypoint.sh +++ b/17/entrypoint.sh @@ -31,4 +31,4 @@ if [ ! -s "$PGDATA/PG_VERSION" ] && [ -n "$POSTGRES_REPLICATION_USER" ]; then fi echo "INFO: starting postgres" -docker-entrypoint.sh "$@" +exec docker-entrypoint.sh "$@" diff --git a/18/entrypoint.sh b/18/entrypoint.sh index 55622d9..0479f07 100755 --- a/18/entrypoint.sh +++ b/18/entrypoint.sh @@ -31,4 +31,4 @@ if [ ! -s "$PGDATA/PG_VERSION" ] && [ -n "$POSTGRES_REPLICATION_USER" ]; then fi echo "INFO: starting postgres" -docker-entrypoint.sh "$@" +exec docker-entrypoint.sh "$@"