This is the multi-page printable view of this section. .
Documentation
-
1: Quick Start
-
2: Namespace Isolation
-
3: Backup
-
4: Restore
-
5: Replica (read-only standby)
-
6: Failover: Replica Promotion
-
7: PostgreSQL Extensions
-
8: Clone
-
9: Data Import/Export
-
10: exec and psql
-
11: Destroy
-
12: Administration
Complete guide to managing PostgreSQL databases with pgcli.
- Quick Start — Install and basic usage
- Backup — Snapshots and pgBackRest management
- Restore — Point-in-time recovery (PITR)
- Replication — Read-only replicas and failover
- Extensions — Install and manage PostgreSQL extensions
1 - Quick Start
Get pgcli up and running in minutes.
Installation
Install pgcli with a single command:
This script will:
- Download the latest pgcli binary for your platform
- Install it to
/usr/local/bin(or~/.local/binif no sudo) - Add pgcli to your PATH
Initialize Configuration
This creates ~/.pgcli/pg.yaml with sensible defaults including:
- A default instance named
default - Data directory at
/data/pg/default - Auto-assigned ports starting from 35432
Start Instance
The output shows the connection URL, admin password, and backup status.
Connect to Your Database
Basic Operations
Multi-Instance
Multiple Config Files (Isolation)
For isolated testing environments or one config per project on the same host,
generate a separate config file per environment with a distinct --namespace
and disjoint port ranges.
| Parameter | Default | Meaning |
|---|---|---|
--namespace |
default |
Container name prefix: pgcli-pg-<namespace>-<instance> |
--pg-start-port |
35432 |
First PG host port; instances get sequential ports |
--pg-ssh-port |
42201 |
First SSH host port; sequential from here |
Planning tips:
- Always pass an explicit
--namespaceto avoid container name clashes. - Port ranges must not overlap between configs on one host.
- The namespace is baked into container names at creation time. Changing it later requires
pg destroyand re-init.
Interactive psql Session
Inside the interactive psql shell you get:
- SQL queries with history and tab completion
- psql meta-commands (
\dt,\du,\l, etc.) \qto quit
Container Shell
The shell runs as root inside the container, giving full access to:
- PostgreSQL data directory (
/var/lib/postgresql/data) - Configuration files
- Log files (
/var/log/postgresql/) - All system tools and utilities
Next Steps
- Learn about backup and restore
- Set up replication for high availability
- Explore extensions management
- Understand administration
2 - Namespace Isolation
Namespaces allow you to create completely isolated environments on a single host, perfect for separating production, development, and testing environments without container name conflicts or port collisions.
What is a Namespace?
A namespace is a prefix applied to all container names within a configuration file. This isolation mechanism ensures that:
- Container names don’t clash: Each namespace gets its own container prefix
- Port ranges are separate: Each config file allocates ports from its own range
- Backup containers are isolated: Each namespace has its own pgBackRest container
- Configuration files are independent: Each environment uses a separate config file
Use Case: Production and Development Environments
A common scenario is running production and development environments on the same server:
This creates two completely isolated environments:
| Environment | Config File | Container Prefix | PG Port Range | SSH Port Range |
|---|---|---|---|---|
| Production | ~/.pgcli-prod/pg.yaml |
pgcli-pg-prod-* |
35432+ | 42201+ |
| Development | ~/.pgcli-dev/pg.yaml |
pgcli-pg-dev-* |
38000+ | 43000+ |
Managing Multiple Environments
Use the -c flag to specify which configuration file to use:
How It Works
Container Naming
With namespace prod and instance app-db:
- Instance container:
pgcli-pg-prod-app-db - Backup container:
pgcli-backup-prod - Network:
pgcli-net-prod(if using separate networks)
Without namespace (or --namespace ""):
- Instance container:
pgcli-pg-default-app-db - Backup container:
pgcli-backup-default
Port Allocation
Each instance in a config file gets sequential ports:
- First instance:
pg_start_port(e.g., 35432) - Second instance:
pg_start_port + 1(e.g., 35433) - And so on…
Same for SSH ports used by pgBackRest.
Configuration Persistence
The namespace and port ranges are saved in the config file:
Best Practices
1. Always Use Explicit Namespaces
Never rely on the default namespace when running multiple configs on one host:
2. Use Disjoint Port Ranges
Ensure port ranges don’t overlap between configs:
Leave headroom for multiple instances within each environment.
3. Namespace is Baked into Container Names
The namespace is embedded in container names at creation time. Changing it later breaks the link:
4. Use Shell Aliases for Convenience
Create aliases to avoid typing -c repeatedly:
Advanced: Multiple Environments with Replicas
You can even set up isolated replication environments:
Each environment maintains its own replication slots, backup stanzas, and data directories.
Troubleshooting
Container Name Conflicts
Cause: Two configs using the same namespace.
Solution: Use different namespaces or destroy the conflicting instance first.
Port Already in Use
Cause: Port ranges overlap between configs.
Solution: Use disjoint port ranges with sufficient spacing.
Instance Not Found After Namespace Change
Cause: Changed namespace in config file after creating instances.
Solution: Either destroy and recreate instances, or revert the namespace change.
Summary
Namespaces provide complete isolation for multiple environments on a single host:
- Separate configs: Each environment gets its own
pg.yaml - Distinct namespaces: Prevents container name collisions
- Disjoint ports: Avoids port conflicts
- Independent operation: Each environment managed separately with
-c
Perfect for running production, development, testing, and staging environments on the same server without interference.
3 - Backup
Snapshots
Snapshot types:
full— Complete backup (default, self-contained)diff— Changes since last full backupincr— Changes since last backup
Shared Backup Container
All instances share a single pgbackrest container; each instance gets its own stanza in the repository.
Backup infrastructure (network, image, directories, config, container) is prepared automatically on pg start; run pg backup setup manually to reinitialize, e.g. after changing the base directory.
4 - Restore
Point-in-Time Recovery (PITR)
Restore to any point in time after the first backup.
Time formats:
2026-08-26 15:30:00+08:00— with timezone offset2026-08-26 15:30:00+08— timezone hour only2026-08-26 15:30:00Z— UTC2026-08-26 15:30:00— assumed UTC
Recovery workflow: Stop → Restore → Start → WAL replay to target time
Note: After --promote, create a new full snapshot before further PITR.
5 - Replica (read-only standby)
Create a read-only physical replica of an existing instance. The replica continuously streams WAL from its primary via PostgreSQL physical replication and serves read-only queries — useful for read/write splitting, reporting, or as a warm standby.
What happens
- Pre-flight check — the primary must be running (verified before any config is written; a stopped primary fails with no side effects)
- Register — a new instance entry is added with the primary’s database name and password (see Notes), PITR disabled, and
replica_ofset to the primary - Replication setup — on the primary:
pg_hba.confgainshost replicationentries for loopback and RFC1918 ranges (idempotent)- a physical replication slot
pgcli_r_<name>is created, reserving WAL so the replica can never fall behind WAL recycling
- Base backup —
pg_basebackup -Rcopies the primary’s data directory into the replica’s data dir, writingprimary_conninfo(with password) andstandby.signalso the replica boots in standby mode - Start — the replica container starts and streams WAL continuously
Verify
Destroy
Destroying a replica is a 2-step process:
Step 1 must run before step 2: PostgreSQL refuses to drop a slot that is still being streamed (replication slot is active), so the replica must be destroyed first to close its streaming connection.
Note: If the primary is a local pgcli-managed instance (same host), Step 2 can be skipped —
destroyautomatically cleans up the slot. Step 2 is required when the primary is not accessible from the replica host.
Cross-network replicas
The same-host flow above assumes primary and replica share one server (one podman daemon, one network). For a replica on another host, pgcli runs one command on each side — no SSH, the only cross-machine information is what you pass as parameters:
Getting the primary DSN
If the primary is a pgcli-managed instance, get its connection info with pg status:
Then replace 127.0.0.1 with the primary host’s IP as seen from the replica host (e.g. 10.241.20.50) — the user, password and database are used as-is. Note the primary host must accept TCP connections on that port from the replica host (firewall/security group).
What each side does:
- Primary side (
--replica-host <ip|hostname>): only prepares the primary — nothing is created locally. It appends ahost replication all <addr>entry topg_hba.confand creates the physical slotpgcli_r_<name>, then prints the exact--primary-dsncommand to run on the replica host. IPs get a/32(/128for IPv6) mask; hostnames are written as-is. An IP already inside a managed RFC1918 range (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) is skipped as redundant. Idempotent — re-running adds no duplicate lines. - Replica side (
--primary-dsn): first verifies the slot exists on the primary (validates connectivity and ordering — running before the primary side fails with an actionable message and no side effects), then registers the instance, runspg_basebackupfrom the DSN over the network (host networking), and starts the standby.
The replica side runs only on the replica host: user, database and password of the replica instance come from the DSN (physical replication copies pg_authid, so the local password must equal the primary’s). The primary name is given with --primary-name and recorded as replica_of; it does not have to exist in the replica host’s config, and -i is not used for the remote primary — if given, it keeps its strict meaning and must reference a real local instance.
Destroy is symmetric, one command per host — in this order:
Step 1 must run before step 2: PostgreSQL refuses to drop a slot that is still being streamed (replication slot is active), so the replica must be destroyed first to close its streaming connection.
Automatic slot cleanup: When a cross-host replica has PrimaryDSN set, destroy automatically attempts to drop the replication slot on the remote primary via DSN. If the primary is reachable, no manual step 2 is needed. replica drop is idempotent — re-running when the slot is already gone succeeds as a no-op.
Non-pgcli primary
The primary does not have to be managed by pgcli — the replica side works against any PostgreSQL server, as long as the primary side has been prepared manually (the slot check only verifies the slot exists, not who created it):
- Allow replication from the replica host in
pg_hba.conf, then reload (SELECT pg_reload_conf()): - Create the physical slot with the exact name
pgcli_r_<replica-name>(the replica side checks this name):Requireswal_level = replica(orlogical) and a user withREPLICATIONprivilege — the DSN user.
Then the replica-side command is unchanged:
On destroy, there is no pgcli on the primary side — after pg destroy -i ro1, drop the slot manually:
If a base backup fails (e.g. network hiccup), destroy the replica and re-run the replica-side command — the slot and hba entry on the primary side remain valid.
Notes
- Read-only — the replica rejects all writes (
cannot execute INSERT in a read-only transaction). To make it writable you would promote it (pg_ctl promote), which is not exposed as a pgcli command yet - Same data, same password — physical replication is a byte-for-byte copy of the primary, including
pg_authid. The replica’s admin password and database name are therefore identical to the primary’s; only container name, port and data directory differ. With--dsn-style connections use the replica’s port - PITR disabled on replicas — a standby archives nothing and is not registered with the pgBackRest backup container; backups run on the primary
- Primary must be running — both for initial creation (
pg_basebackup) and for continuous streaming; if the primary restarts, the replica reconnects automatically (slot showsactive) - Lag display —
replica listlag (now() - pg_last_xact_replay_timestamp()) grows while the primary is idle; it drops back to zero on the next replicated transaction. This is expected idle behavior, not drift - Idempotent start — repeated
pg start -i ro1skips the base backup when the data directory is already initialized
6 - Failover: Replica Promotion
Promote a replica to become the new primary when the current primary fails. pgcli provides a 3-step manual failover workflow — each step runs on its respective host, no auto-detection of same-host vs cross-host topology.
Overview
After failover (promote ro1 → new primary):
3-Step Failover
Each step is an independent command. Run them in order, on their respective hosts.
Step 1: pg replica promote <name>
Run on the host of the replica being promoted to primary.
What happens:
- Validates the instance is a replica (
ReplicaOfis set) and the container is running - Calls
pg_promote()(PostgreSQL 12+ native promotion — no container restart) - Waits for recovery to end (usually sub-second)
- Cleans up
primary_conninfofrompostgresql.auto.confviaALTER SYSTEM RESET - Updates config: clears
ReplicaOfandPrimaryDSN, enablesPITR - Automatically initializes PITR:
- pgBackRest stanza creation
archive_mode/archive_commandconfiguration- PostgreSQL restart to apply postmaster-level parameters
- Prints next-step instructions
Idempotent: If the replica is already promoted (e.g. from a manual pg_ctl promote), the command skips to config update.
Step 2: pg replica drop <name> -i <old-primary>
Run on the old primary host to clean up the replication slot. This step is only needed in specific scenarios.
This drops the physical replication slot pgcli_r_ro1 on the old primary. Without cleanup, the slot would hold WAL indefinitely until the primary runs out of disk space.
When to run:
| Scenario | Action | Why |
|---|---|---|
| Old primary is permanently lost | Skip | Slot is gone with the server |
| Plan to demote old primary to replica | Skip | repoint destroys the data directory (including pg_replslot/), all slots are implicitly removed |
| Old primary recovered, keep running as independent primary | Must run | Slot holds WAL indefinitely; without cleanup the disk will eventually fill up |
| Old primary recovered but will be shut down | Optional | No harm in skipping if the instance will not run again |
Keep the old primary as-is? If you want to preserve the old primary with its original data (e.g. for forensic analysis or as a read-only archive), you can simply leave it alone — do not run
droporrepointon it. The old primary keeps running as an independent instance with stale data. Just be aware that the replication slot for the promoted replica still exists and will accumulate WAL; you may want to drop just that specific slot (pg replica drop ro1 -i pg01) while leaving everything else untouched.
Step 3: pg replica repoint <name> --primary-dsn <dsn> --primary-name <name>
Run on each remaining replica host to re-point it to the new primary.
What happens:
- Queries the new primary’s extensions via DSN (
pg_extensioncatalog) - If non-builtin extensions exist (e.g. pg_cron, timescaledb), builds a local
-extimage with matching packages - Stops the old replica container and destroys its data directory
- Creates a replication slot on the new primary via DSN
- Updates config:
ReplicaOf,PrimaryDSN,ImageTag,Extensions, disablesPITR - Re-initializes via
pg_basebackup -Rfrom the new primary - Starts the replica container in standby mode
Why destroy + rebuild instead of ALTER SYSTEM SET?
After promotion, the new primary advances to a new timeline. Other replicas on the old timeline cannot simply change primary_conninfo — PostgreSQL rejects the connection with:
The only safe approach is a full pg_basebackup from the new primary.
Getting the Primary DSN
Get the new primary’s connection string from pg status on the promoted replica’s host:
Replace 127.0.0.1 with the new primary host’s IP reachable from the replica host (e.g. 10.241.21.97).
Demoting the Old Primary
When the old primary recovers, you can rejoin it as a replica of the new primary using the same repoint command:
This works even though pg01 was a primary (no ReplicaOf set). The command:
- Stops pg01 and destroys its data (including old PITR stanza)
- Creates a replication slot for pg01 on the new primary
- Sets
ReplicaOf = "ro1",PITR.Enabled = false - Re-initializes from the new primary via
pg_basebackup
After repoint, pg01 streams WAL from the new primary as a read-only replica — no WAL archiving, no backups.
Extension Sync
When a replica is repointed to a new primary, pgcli automatically synchronizes extensions:
- Query — Connects to the new primary via DSN and queries
pg_extensionfor installed extensions - Filter — Identifies non-builtin extensions (those requiring external packages, e.g. pg_cron, pgmq, timescaledb)
- Build — If non-builtin extensions exist, builds a local
-extimage:- If a local
-extimage already exists, installs missing packages on top (reuses Pigsty repo — fast) - If no
-extimage exists, builds from the base image with Pigsty repo setup apt-get installis idempotent — installing an already-present package is a no-op
- If a local
- Apply — On replica start,
ApplyExtensionswritesshared_preload_librariestopostgresql.conf - Skip CREATE EXTENSION — Replicas are read-only; extensions are replicated from the primary via
pg_basebackup+ WAL streaming
This ensures the replica container has the required shared libraries (e.g. pg_cron) that are referenced in postgresql.auto.conf.
Same-Host vs Cross-Host
pgcli does not auto-detect topology. You choose where to run each command:
| Scenario | Step 1 | Step 2 | Step 3 |
|---|---|---|---|
| All on one host | pg replica promote ro1 |
pg replica drop ro1 -i pg01 |
pg replica repoint ro2 --primary-dsn "postgres://...@127.0.0.1:..." --primary-name ro1 |
| Primary + replicas split across hosts | On replica host | On old primary host | On each replica host with the new primary’s network IP |
| Mixed | On the respective host | On old primary host | On each replica host |
The --primary-dsn must use an IP/hostname reachable from the host where repoint runs.
Complete Example
Cross-Host Example
Cascading Replication
A replica can itself serve as a primary for downstream replicas, forming a cascading chain. This reduces load on the primary and enables hierarchical topologies.
How It Works
-
Create a replica of a replica: Use the replica as the
-itarget -
WAL propagation:
- ra2 streams WAL from ra3
- ra2_ro1 streams WAL from ra2
- Data flows: ra3 → ra2 → ra2_ro1
-
Replication slots: Each link maintains its own slot
- ra3 has slot
pgcli_r_ra2 - ra2 has slot
pgcli_r_ra2_ro1
- ra3 has slot
Benefits
- Reduced primary load: Only direct replicas connect to primary
- Geographic distribution: Primary → regional replica → local replicas
- Network efficiency: Local replicas can share a regional upstream
Limitations
- Increased latency: Each hop adds replication delay
- Cascading failures: If ra2 fails, ra2_ro1 loses its upstream
- Promotion complexity: Promoting ra2_ro1 requires repointing it to a new primary
Verify Cascading
Failover with Cascading
If ra2 (middle node) fails:
If ra3 (primary) fails and ra2 is promoted:
Notes
- pg_promote() — PostgreSQL 12+ native function, no container restart required. The instance exits recovery in-place and becomes read-write immediately
- Timeline divergence — After promotion, the new primary is on a new timeline. Other replicas cannot be re-pointed with
ALTER SYSTEM SET primary_conninfo— they must be rebuilt viapg_basebackup - PITR on promoted replica — After promotion, run
pg startto create the pgBackRest stanza and enable WAL archiving. The promoted replica has no prior backup history - Replication slots — The old primary’s slot for the promoted replica becomes stale after promotion.
pg replica dropcleans it up. If the old primary is demoted to a replica,repointdestroys the old data and the stale slot is no longer referenced - Extensions — Replica containers inherit
shared_preload_librariesfrom the primary viapostgresql.auto.conf. The repoint command ensures the local image has the required extension packages before rebuilding the replica - CREATE EXTENSION skipped — Replicas are read-only;
pg_basebackupcopies the extension metadata from the primary, soCREATE EXTENSIONis not needed (and would fail with “cannot execute CREATE EXTENSION in a read-only transaction”)
7 - PostgreSQL Extensions
pgcli supports installing and managing PostgreSQL extensions from the Pigsty DEB repository.
How It Works
Extensions are baked into a derived container image:
pg extension installbuilds a new image (based on the current image + Pigsty repo + extension packages)- Stops and removes the old container
- Recreates the container from the new image (host data volumes are preserved)
- Updates
image_tagin the config file
Benefits of this approach:
- Extensions survive container rebuilds (baked into the image layer)
pg startdoes not need to runapt-get installon every boot- Extension files are persistent and decoupled from the container lifecycle
Commands
Install Extensions
Restart confirmation: Extensions requiring shared_preload_libraries (e.g., pg_stat_statements, pg_cron) need a PostgreSQL restart. By default, you will be prompted for confirmation:
If you decline the restart, you can apply the changes later:
List Installed Extensions
Example output:
- managed: tracked by pgcli (recorded in config, included in image)
- unmanaged: manually installed extensions (not tracked in config)
Remove Extensions
Workflow:
DROP EXTENSION IF EXISTS pgmq- Update config and
shared_preload_libraries - No image rebuild — the
-extimage is shared across instances and packages are never uninstalled
Restart confirmation: If removing extensions that require shared_preload_libraries (e.g., pg_stat_statements, pg_cron), you will be prompted for confirmation before restarting:
If you decline the restart, you can apply the changes later:
View Available Extensions
Lists all 440 known extensions:
- 45 builtin (contrib, already in the base image — no image build needed)
- 395 Pigsty catalog (from Pigsty DEB repo, requires image build)
Built-in Extension Catalog
Requires shared_preload_libraries (restart on install)
| Extension | Description |
|---|---|
pg_stat_statements |
SQL performance analysis |
pg_cron |
Scheduled job execution |
pg_hint_plan |
Query hints |
pg_stat_monitor |
Advanced performance monitoring |
pg_qualstats |
Query predicate statistics |
pg_stat_kcache |
Kernel-level performance stats |
pg_wait_sampling |
Wait event sampling |
pg_track_settings |
Configuration change tracking |
timescaledb |
Time-series database extension |
No shared_preload_libraries (no restart needed)
| Extension | Description |
|---|---|
uuid-ossp |
UUID generation functions |
pgmq |
Lightweight message queue |
hstore |
Key-value pair storage |
pgcrypto |
Cryptographic functions |
tablefunc |
Crosstab functions |
btree_gist |
B-tree GiST index support |
btree_gin |
B-tree GIN index support |
pg_trgm |
Trigram similarity matching |
unaccent |
Accent removal functions |
fuzzystrmatch |
Fuzzy string matching |
intarray |
Integer array operations |
isn |
ISBN/ISSN/EAN standard number types |
pg_repack |
Online table reorganization |
pg_squeeze |
Table space reclamation |
pg_partman |
Partition management |
pgvector |
Vector similarity search |
postgis |
Geospatial data support |
Extensions Outside the Catalog
Only extensions in the catalog (builtin + Pigsty) can be installed via pg extension install. Unknown extension names are rejected before the build starts:
Full catalog: https://pigsty.cc/ext/list/
Configuration
After installing extensions, the config is updated:
The image_tag points to the derived image containing all installed extensions.
Shared Preload Libraries
Extensions requiring shared_preload_libraries are automatically configured in postgresql.conf:
This is a postmaster-level parameter; PostgreSQL must be restarted after changes.
Troubleshooting
Extension Install Failure
Cause: Extension name is not in the builtin contrib list or Pigsty catalog.
Resolution:
- Verify the extension name:
pg extension available - Check the Pigsty catalog: https://pigsty.cc/ext/list/
- Note the exact SQL extension name (e.g.,
vectornotpgvector)
CREATE EXTENSION Failure
The extension is installed but not tracked in config. You can safely ignore this, or manually add it to the config:
Shared Preload Library Conflict
If shared_preload_libraries was manually edited in postgresql.conf, pgcli’s sentinel block will overwrite it.
Resolution: Remove the manual configuration and let pgcli manage it.
Notes
- Extension count: 45 builtin (contrib) + 395 Pigsty catalog = 440 total known extensions
- Image size: Each extension adds 10-50MB to the image, but Pigsty packages are optimized
- Build time: First extension install takes 1-3 minutes (download + build); subsequent installs are faster (cache hits)
- Replica behavior: Replicas can install extensions, but
CREATE EXTENSIONwill be rejected (read-only). Install on the primary; replicas sync via physical replication - Extension upgrades:
ALTER EXTENSION ... UPDATE TO ...is not yet supported; run manually viapg exec
8 - Clone
Create a new instance whose data is copied from an existing one, streamed directly — no temp file on disk.
What happens
- Pre-flight check — the source is verified before anything is created:
- Local instance: container must be running
--dsn: an authenticatedSELECT 1must succeed (catches wrong password, unreachable host)
- Create — a new instance entry is added to the config with a random password, its own container name, data directory and auto-assigned port
- Start — the new instance is started (same workflow as
pg start) - Stream — source data is piped to the target with live transfer progress shown once per second
Notes
- The source instance must be running (or the
--dsntarget reachable); a bad source fails immediately with no side effects - The new instance name must not already exist in config
--dsnand--instanceare mutually exclusive: with--dsnthe connection string determines host, port and database- The new instance gets a fresh random password — find it in the clone output or
pg status -i <name> - Logical copy only (schema + data); for large databases a physical approach may be faster
9 - Data Import/Export
Export and import databases to dump files. Supports custom format (recommended) and plain SQL, with automatic gzip compression. Also supports piping between instances.
Format comparison:
| Feature | Custom (.dump) |
SQL (.sql) |
|---|---|---|
| Import speed | Faster (binary format) | Slower (text format) |
| File size | Smaller (compressed) | Larger (plain text) |
| Human-readable | No | Yes |
| Selective restore | Yes (specific tables) | No |
| Best for | Migration, backup, large databases | Version control, CI seed data, manual editing |
Format detection: Uses magic bytes (content-based) with extension fallback.
- Files starting with
PGDMP→ custom format .sqlor.sql.gz→ plain SQL format.gzextension or gzip magic bytes (0x1f 0x8b) → automatic decompression- Extension is used as fallback if content detection fails
Remote databases (–dsn): Connect to any PostgreSQL instance using a connection string.
- No local PostgreSQL installation needed
- Works with local-to-remote, remote-to-local, and remote-to-remote migrations
- Supports all the same flags as local instances (
-o,-d,--clean,-v,--compress)
Note on existing data: Importing into a database with existing tables will fail unless you use the --clean flag, which drops objects before restoring. Use --clean when importing into a database that already contains data.
Use cases:
- Migrate data between instances:
pg export -i proj01 | pg import -i proj02 - Cross-host migration:
pg export -i proj01 | pg import --dsn postgres://user:pass@remote:5432/db - Share database with team:
pg export -i proj01 -o dump.dump.gz(compressed, smaller file) - Backup before major changes:
pg export -i proj01 -o pre-migration.sql.gz - CI/CD pipelines: export test data, import into fresh test databases
10 - exec and psql
Two ways to run SQL against an instance: pg exec for one-shot SQL or container commands, pg psql for interactive sessions.
pg exec
SQL mode (default)
Arguments without -- are executed as SQL via psql, using the instance’s configured user and database.
Container command mode (after –)
Arguments after -- are run directly inside the container (as root).
Remote database (–dsn)
Execute SQL against any database reachable via a connection string, using a temporary container. --dsn only supports SQL mode; container commands require a local instance.
pg psql
Interactive session
Inside the shell you get full psql features: SQL with history and tab completion, meta-commands (\dt, \du, \l), and \q to quit.
Non-interactive (scripts)
Switch to postgres superuser
Some administrative tasks (e.g., creating certain extensions, modifying system-level settings) require postgres superuser privileges. Use -- to pass psql arguments and switch user:
Recommended approach: Use the instance default user (admin) for daily operations, and switch to postgres only when superuser privileges are needed. This is safer and more convenient than modifying config files or restarting containers.
Example scenarios:
Remote database (–dsn)
Rules
--dsnand--instanceare mutually exclusive: the connection string determines host, port and database, so-iis rejected to avoid silent misuse.- With
--dsn, the database is the path part of the URL:postgres://user:pass@host:5432/mydbconnects tomydb. To use another database, change the path. - With a local instance,
--passes raw psql arguments (including-d/-U), overriding the instance defaults.
11 - Destroy
Destroy stops and removes the container, then removes the instance from the configuration file. By default, host data directories are preserved. Use --clean-data to also remove data, WAL archives, and the pgBackRest repository stanza.
Basic Usage
What Happens
When you run pg destroy:
- Stop container — PostgreSQL is gracefully shut down
- Remove container — The Podman container is deleted
- Remove configuration — The instance entry is removed from
~/.pgcli/pg.yaml - Preserve data (by default) — Host data directory at
--base-dir/<instance>is kept
With --clean-data:
- All of the above, plus:
- Remove host data — The data directory is deleted
- Remove WAL archives — Any WAL files on the host are removed
- Remove backup stanza — The pgBackRest repository stanza for this instance is removed
Recreating an Instance
After destroying, you can recreate the instance with a fresh start:
Important: Without --clean-data, the old data directory is preserved. When you recreate the instance, PostgreSQL will use the existing data, and init.sh (which creates users and sets the admin password) does not run again. This can cause issues if:
- The data was created with a different user or password
- You changed the default user in configuration
- You want a completely fresh start
Use --clean-data when you need a clean slate.
Confirmation Prompt
By default, pg destroy asks for confirmation before proceeding:
Use --force to skip the prompt:
Use Cases
1. Clean Restart After Configuration Change
If you changed the default user, password, or PostgreSQL version in the config, destroy and recreate:
2. Remove Test Instance
After testing, remove an instance you no longer need:
3. Troubleshooting Corrupted State
If an instance is in a bad state (e.g., failed to start, corrupted data), destroy and recreate:
4. Free Up Resources
Destroy instances you’re not actively using to free up:
- Podman containers (CPU and memory)
- Host disk space (with
--clean-data) - Configuration file entries
Relationship with Replicas
When destroying a replica, the replication slot on the primary is not automatically removed. You need to clean it up separately:
This two-step process ensures you don’t accidentally lose the slot if you plan to recreate the replica later.
Safety Considerations
- Data Loss:
--clean-datapermanently deletes all data, WAL, and backups for the instance. Use with caution. - No Undo: Once destroyed, the instance cannot be recovered unless you have external backups.
- Configuration Loss: The instance entry is removed from the config file. If you need the configuration later, back it up first.
Flags
| Flag | Default | Description |
|---|---|---|
--force |
false |
Skip confirmation prompt |
--clean-data |
false |
Also remove host data, WAL archives, and backup stanza |
-i, --instance |
default |
Instance name to destroy |
Related Commands
pg create— Create a new instancepg start— Start an instancepg stop— Stop an instance (container remains)pg replica drop— Remove replication slot from primary
12 - Administration
Shell Completion
Enable tab completion for commands, flags, and instance names.
Bash
Zsh
Fish
PowerShell
PostgreSQL Configuration
Modify PostgreSQL runtime parameters via pg exec with ALTER SYSTEM, then reload:
Note: Some parameters (e.g. shared_buffers, max_connections) require a restart rather than reload. Use pg stop && pg start to apply those changes.
Configuration File Management
Inspect or validate the config file (~/.pgcli/pg.yaml by default, override with -c).
Init generates a default config; --add creates a named instance in the same file, -o writes to a custom path:
Isolation parameters for running multiple configs on one host (see Quick Start for planning):
| Parameter | Default | Meaning |
|---|---|---|
--namespace |
default |
Prefix for container names: pgcli-pg-<namespace>-<instance>, backup container pgcli-backup-<namespace>. Pass --namespace "" to keep legacy names without a prefix |
--pg-start-port |
35432 |
First PG host port in the allocation range |
--pg-ssh-port |
42201 |
First SSH host port in the allocation range |
All three are saved into the config file (namespace, pg_start_port, pg_ssh_port); use disjoint port ranges across configs so allocations never collide.