Files for properly setting up PostgreSQL replication between docker containers on MacOS.

Grab the zipfile with the configuration files from here and unzip it into the working folder. There should now be a subdirectory with all the configuration files in it.

Explanation of the files:
3 YAML files,
The docker config for the Master node:
docker-compose-1.yml
name: postgres1
services:
    postgres:
        user: root
        stdin_open: true
        tty: true
        container_name: postgres1
        networks:
            - postgresnet
        environment:
            - POSTGRES_USER=postgresadmin
            - POSTGRES_PASSWORD=g_97Tgs2fjru857_dfh
            - POSTGRES_DB=postgresdb
            - PGDATA=/data/pgdata
        volumes:
            - p1pgdata:/data
            - ./postgres1/config:/config
            - ./postgres1/dump:/dump
            - ./postgres1/init:/docker-entrypoint-initdb.d
        ports:
            - 5401:5432
        image: postgres:16
        command: "postgres -c 'config_file=/config/postgresql.conf'"
networks:
    postgresnet:
        name: postgresnet
        external: true
volumes:
    p1pgdata:

The docker config of the Replica before replication runs:
docker-compose-2.yml
name: postgres2
services:
    postgres:
        user: root
        stdin_open: true
        tty: true
        container_name: postgres2
        networks:
            - postgresnet
        environment:
            - POSTGRES_USER=postgresadmin
            - POSTGRES_PASSWORD=g_97Tgs2fjru857_dfh
            - POSTGRES_DB=postgresdb
            - PGDATA=/data/pgdata
        volumes:
            - p2pgdata:/data
            - ./postgres2/config:/config
            - ./postgres2/dump:/dump
            - ./postgres2/init:/docker-entrypoint-initdb.d
        ports:
            - 5402:5432
        image: postgres:16
        command: "postgres -c 'config_file=/config/postgresql.conf'"
networks:
    postgresnet:
        name: postgresnet
        external: true
volumes:
    p2pgdata:

The docker config of the Replica after replication runs:
docker-compose-2R.yml
name: postgres2
services:
    postgres:
        user: root
        stdin_open: true
        tty: true
        container_name: postgres2
        networks:
            - postgresnet
        environment:
            - POSTGRES_USER=postgresadmin
            - POSTGRES_PASSWORD=g_97Tgs2fjru857_dfh
            - POSTGRES_DB=postgresdb
            - PGDATA=/data/pgdataR
        volumes:
            - p2pgdata:/data
            - ./postgres2/config:/config
            - ./postgres2/dump:/dump
            - ./postgres2/init:/docker-entrypoint-initdb.d
        ports:
            - 5402:5432
        image: postgres:16
        command: "postgres -c 'config_file=/config/postgresql.conf'"
networks:
    postgresnet:
        name: postgresnet
        external: true
volumes:
    p2pgdata:

The init shellscript that is executed before the database first starts. It puts the .pgpass file in the right place and sets some ownership.
init.sh
unset PGPASSWORD
PG_PASS=/config/.pgpass
cat /config/pgpass > $PG_PASS
chmod 600 $PG_PASS

chown -R postgres:postgres /config
chown -R postgres:postgres /dump

The init SQL that is executed after the database first starts. It creates the replication user and a replication slot.
init.sql
CREATE USER replicator REPLICATION LOGIN ENCRYPTED PASSWORD '0TGcrAKxwN72tWAMq90PwTZVHm3VgZMg';
DO LANGUAGE PLPGSQL $$ begin PERFORM pg_create_physical_replication_slot('replication_slot_1'); end; $$

The pg_hba.conf file contains the access control.
pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256

# remote replication (wal streaming)
host    replication     replicator      127.0.0.1/32            trust
host    replication     replicator      10.0.0.0/8              scram-sha-256
host    replication     replicator      172.16.0.0/12           scram-sha-256
host    replication     replicator      192.168.0.0/16          scram-sha-256

# all else must authenticate
host    all             all             all                     scram-sha-256

The pg_ident.conf file contains user name maps, but is empty
pg_ident.conf
# PostgreSQL User Name Maps

# MAPNAME       SYSTEM-USERNAME         PG-USERNAME

The pgpass file is for direct access to the server on the other side, it enables the replication-user (replicator) the password to access the listed servers.
pgpass
# HOSTNAME:PORT:DATABASE:USERNAME:PASSWORD

localhost:*:*:replicator:0TGcrAKxwN72tWAMq90PwTZVHm3VgZMg
postgres1:*:*:replicator:0TGcrAKxwN72tWAMq90PwTZVHm3VgZMg
postgres2:*:*:replicator:0TGcrAKxwN72tWAMq90PwTZVHm3VgZMg

The postgresql_m.conf file is the main configuration file for the Master node.
postgresql_m.conf
# -----------------------------
# PostgreSQL configuration file
# -----------------------------

data_directory = '/data/pgdata'
hba_file = '/config/pg_hba.conf'
ident_file = '/config/pg_ident.conf'

port = 5432
listen_addresses = '*'
max_connections = 100

dynamic_shared_memory_type = posix
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'

#locale settings
lc_messages = 'en_US.utf8'			# locale for system error message
lc_monetary = 'en_US.utf8'			# locale for monetary formatting
lc_numeric = 'en_US.utf8'			# locale for number formatting
lc_time = 'en_US.utf8'				# locale for time formatting

default_text_search_config = 'pg_catalog.english'

checkpoint_completion_target = 0.9
default_statistics_target = 100
effective_cache_size = 3GB
effective_io_concurrency = 200
huge_pages = off
maintenance_work_mem = 256MB
max_parallel_maintenance_workers = 2
max_parallel_workers = 4
max_parallel_workers_per_gather = 2
max_wal_size = 4GB
max_worker_processes = 4
min_wal_size = 80MB
random_page_cost = 1.1
shared_buffers = 1GB
wal_buffers = 16MB
work_mem = 2560kB               # maintenance_work_mem / max_connections

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 20000
pg_stat_statements.track = all

wal_level = replica
wal_log_hints = on
max_wal_senders = 3
wal_keep_size = 1024MB

hot_standby = on

The postgresql_s.conf file is the main configuration file for the Replica node before the replication has started, when it still is in single-mode.
postgresql_s.conf
# -----------------------------
# PostgreSQL configuration file
# -----------------------------

data_directory = '/data/pgdata'
hba_file = '/config/pg_hba.conf'
ident_file = '/config/pg_ident.conf'

port = 5432
listen_addresses = '*'
max_connections = 100

dynamic_shared_memory_type = posix
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'

#locale settings
lc_messages = 'en_US.utf8'			# locale for system error message
lc_monetary = 'en_US.utf8'			# locale for monetary formatting
lc_numeric = 'en_US.utf8'			# locale for number formatting
lc_time = 'en_US.utf8'				# locale for time formatting

default_text_search_config = 'pg_catalog.english'

checkpoint_completion_target = 0.9
default_statistics_target = 100
effective_cache_size = 3GB
effective_io_concurrency = 200
huge_pages = off
maintenance_work_mem = 256MB
max_parallel_maintenance_workers = 2
max_parallel_workers = 4
max_parallel_workers_per_gather = 2
max_wal_size = 4GB
max_worker_processes = 4
min_wal_size = 80MB
random_page_cost = 1.1
shared_buffers = 1GB
wal_buffers = 16MB
work_mem = 2560kB               # maintenance_work_mem / max_connections

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 20000
pg_stat_statements.track = all

The postgresql_r.conf file is the main configuration file for the Replica node after the replication has started.
postgresql_r.conf
# -----------------------------
# PostgreSQL configuration file
# -----------------------------

data_directory = '/data/pgdataR'
hba_file = '/config/pg_hba.conf'
ident_file = '/config/pg_ident.conf'

port = 5432
listen_addresses = '*'
max_connections = 100

dynamic_shared_memory_type = posix
log_timezone = 'Etc/UTC'
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'

#locale settings
lc_messages = 'en_US.utf8'			# locale for system error message
lc_monetary = 'en_US.utf8'			# locale for monetary formatting
lc_numeric = 'en_US.utf8'			# locale for number formatting
lc_time = 'en_US.utf8'				# locale for time formatting

default_text_search_config = 'pg_catalog.english'

checkpoint_completion_target = 0.9
default_statistics_target = 100
effective_cache_size = 3GB
effective_io_concurrency = 200
huge_pages = off
maintenance_work_mem = 256MB
max_parallel_maintenance_workers = 2
max_parallel_workers = 4
max_parallel_workers_per_gather = 2
max_wal_size = 4GB
max_worker_processes = 4
min_wal_size = 80MB
random_page_cost = 1.1
shared_buffers = 1GB
wal_buffers = 16MB
work_mem = 2560kB               # maintenance_work_mem / max_connections

shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 20000
pg_stat_statements.track = all

primary_slot_name = 'replication_slot_1'
primary_conninfo = 'application_name=standby1 passfile=''/config/.pgpass'' host=postgres1 port=5432 user=replicator'

wal_level = replica
wal_log_hints = on
max_wal_senders = 3
wal_keep_size = 1024MB

hot_standby = on
Note that this configuration has the primary_conninfo line that enables the replication, but it also uses a different data_directory.


Ellert van Koperen, April 2024.