PostGres Database Replication Using Pglogical

Published: (February 24, 2026 at 03:52 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Prerequisites

  • Docker Engine

    sudo systemctl start docker
    sudo systemctl enable docker   # optional – start at boot
  • Docker Desktop on Debian (installed from a .deb package)

    systemctl --user start docker-desktop

Project structure

//docker/

├── Dockerfile.pglogical
└── postgres/
    └── docker-compose.yml

docker-compose.yml

Create the file docker-compose.yml inside /docker/postgres/:

services:
  pg1:
    build:
      context: ..
      dockerfile: Dockerfile.pglogical
    container_name: pg1
    command:
      - postgres
      - -c
      - shared_preload_libraries=pglogical
      - -c
      - wal_level=logical
      - -c
      - max_replication_slots=10
      - -c
      - max_wal_senders=10
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: testdb
      POSTGRES_HOST_AUTH_METHOD: trust
    ports:
      - "5433:5432"
    volumes:
      - pg1_data:/var/lib/postgresql/data
    networks:
      - pgnet

  pg2:
    build:
      context: ..
      dockerfile: DockerFile.pglogical
    container_name: pg2
    command:
      - postgres
      - -c
      - shared_preload_libraries=pglogical
      - -c
      - wal_level=logical
      - -c
      - max_replication_slots=10
      - -c
      - max_wal_senders=10
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: testdb
      POSTGRES_HOST_AUTH_METHOD: trust
    ports:
      - "5434:5432"
    volumes:
      - pg2_data:/var/lib/postgresql/data
    networks:
      - pgnet

networks:
  pgnet:
    driver: bridge

volumes:
  pg1_data:
  pg2_data:

Dockerfile.pglogical

Because the base PostgreSQL image does not contain the pglogical extension, create a custom Dockerfile:

FROM postgres:16

RUN apt-get update \
 && apt-get install -y postgresql-18-pglogical \
 && rm -rf /var/lib/apt/lists/*

Build and start the containers

cd /docker/postgres
docker compose up -d

Verify that the containers are running:

docker ps

You should see something similar to:

CONTAINER ID   IMAGE               COMMAND                  CREATED        STATUS        PORTS                                            NAMES
c61ef5d393cc   postgres-pg1       "docker-entrypoint.s…"   3 days ago     Up 4 minutes  0.0.0.0:5433->5432/tcp, [::]:5433->5432/tcp      pg1
b055528784d5   postgres-pg2       "docker-entrypoint.s…"   3 days ago     Up 4 minutes  0.0.0.0:5434->5432/tcp, [::]:5434->5432/tcp      pg2

Install and enable pglogical inside each container

docker exec -it pg1 psql -U postgres -d testdb

Inside psql:

CREATE EXTENSION pglogical;
\dx   -- verify the extension is installed

You should see an entry similar to:

 List of installed extensions
  Name    | Version |   Schema   | Description
----------+---------+------------+--------------------------------
 pglogical| 2.4.6   | pglogical  | PostgreSQL Logical Replication
 plpgsql  | 1.0     | pg_catalog | PL/pgSQL procedural language

Repeat the same steps for the pg2 container.

Register the databases as pglogical nodes

Run the following in pg1:

SELECT pglogical.create_node(
    node_name := 'pg1',
    dsn := 'host=pg1 port=5432 dbname=testdb user=postgres password=postgres'
);

Run the following in pg2:

SELECT pglogical.create_node(
    node_name := 'pg2',
    dsn := 'host=pg2 port=5432 dbname=testdb user=postgres password=postgres'
);

Create a replication set on pg1

SELECT pglogical.create_replication_set(
    'demo_set',
    replicate_insert := true,
    replicate_update := true,
    replicate_delete := true,
    replicate_truncate := true
);

Create a table and insert baseline rows (pg1)

CREATE TABLE demo_events (
    id SERIAL PRIMARY KEY,
    message TEXT,
    created_at TIMESTAMP DEFAULT now()
);

INSERT INTO demo_events (message)
SELECT 'baseline row ' || g
FROM generate_series(1,5) g;

Add the table to the replication set (pg1)

SELECT pglogical.replication_set_add_table(
    'demo_set',
    'demo_events'
);

Create a subscription on pg2

SELECT pglogical.create_subscription(
    subscription_name := 'sub_pg1',
    provider_dsn := 'host=pg1 port=5432 dbname=testdb user=postgres password=postgres',
    replication_sets := ARRAY['demo_set'],
    synchronize_structure := true,
    synchronize_data := true
);

Wait a few seconds, then verify that the data has been replicated:

SELECT * FROM demo_events;

You should see the five baseline rows that were inserted on pg1.

Test write direction (pg1 → pg2)

Insert a new row on the primary (pg1):

INSERT INTO demo_events (message)
VALUES ('inserted new row');

The row will appear on pg2 automatically. Attempting to write directly to pg2 will raise an error, confirming that pg2 is a read‑only replica.

Conclusion

You now have two PostgreSQL containers replicating data using the pglogical extension. Changes made on the primary (pg1) are automatically propagated to the replica (pg2), providing logical, near‑real‑time replication without the need for physical streaming. Enjoy your synchronized databases! 🎉

0 views
Back to Blog

Related posts

Read more »

DevOps and Vibe Coding: A Journey

Things to Do Map Your Application - Map your application on paper, in a spreadsheet, or using graphics/flowcharts. This is the first step. - Understanding the...

OpenAI just raised $110 billion. Wow

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as we...