psql Connection and Usage

This note is the PostgreSQL equivalent of the SQL Server chapter’s sqlcmd note. The operational purpose is the same: make the command-line client predictable enough that automation, health checks, one-off diagnostics, and emergency access all behave the same way every time. The current lab uses the stoxx-postgres container as both the server and the installed client surface, so every captured example uses docker exec ... psql rather than assuming a separate Windows psql.exe is present on the host.

Current Environment

Before writing automation against PostgreSQL, confirm where the client actually lives, which server endpoint it is reaching, and whether the workstation has a native client at all. The current lab is container-first: the database server listens on port 5432 inside the container, the workstation reaches it on localhost:5434, and every captured psql example in this note was executed from inside stoxx-postgres.

PostgreSQL | psql | client baseline

This subsection verifies the exact psql build shipped in the lab container and confirms the database, role, server version, and data directory that later notes will inherit. The commands are read-only and belong at the very start of any new PostgreSQL workstation or lab validation.

Identify the installed psql binary

Run this as the first client check on any new PostgreSQL host, container, or automation runner. The trigger is simple: if the psql build is unknown, every later assumption about flags, help topics, and client behavior is weaker than it should be. The command runs in a shell, is read-only, and requires only the ability to execute the client binary. Its purpose is to pin the client version before any scripted behavior is trusted.

This command prints the exact psql client version available inside the live lab container.

docker exec stoxx-postgres psql --version
psql (PostgreSQL) 16.13 (Debian 16.13-1.pgdg13+1)

The lab is using the PostgreSQL 16 client that ships in the official Debian-based image, which is the right baseline for a chapter whose server is also PostgreSQL 16.13. That version alignment matters because later client behaviors such as --csv, --help=options, and the exact help text surface are versioned with the client, not with the host OS.

Confirm the current database, role, server version, and data directory

Run this immediately after the first successful connection, or any time there is a risk that you are on the wrong instance, database, or role. It is usually triggered by a new terminal session, a recent rebuild, or a suspicion that the connection target changed underneath automation. The command runs through psql, is read-only, and needs only a valid login path. Its purpose is to prove the identity of the current server and database before a state-changing step is even considered.

Output fieldSource expressionUnit / typeMeaning
database_namecurrent_database()nameThe database currently bound to this session.
role_namecurrent_usernameThe effective SQL role for permission checks in this session.
server_versioncurrent_setting('server_version')textThe server’s advertised PostgreSQL version string.
server_portcurrent_setting('port')textThe server-side listener port inside the current runtime.
data_directorycurrent_setting('data_directory')textThe server’s active cluster data directory.

This command confirms the live session target rather than assuming the right database and cluster were reached.

docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off -c "SELECT current_database() AS database_name, current_user AS role_name, current_setting('server_version') AS server_version, current_setting('port') AS server_port, current_setting('data_directory') AS data_directory;"
 database_name | role_name |         server_version          | server_port |      data_directory      
---------------+-----------+---------------------------------+-------------+--------------------------
 stoxx         | postgres  | 16.13 (Debian 16.13-1.pgdg13+1) | 5432        | /var/lib/postgresql/data
(1 row)

The session is attached to the intended stoxx database as the postgres superuser. The important boundary here is the port distinction: the server itself is listening on 5432 inside the container, but the host entry point that later notes will use from Windows is localhost:5434.

FlagSyntaxDescription
-Vpsql -VPrint the client version and exit.
-Upsql -U postgresConnect as the named database role instead of the OS user name.
-dpsql -d stoxxConnect to the named database.
-Ppsql -P pager=offSet a \pset printing option from the command line.
-cpsql -c "SELECT ..."Execute one SQL command string or one backslash command and then exit.

PostgreSQL | psql | PowerShell and Docker entry paths

This subsection makes the current workstation boundary explicit. The Windows host can reach the server through Docker port forwarding, but the host currently does not have a native psql binary on PATH, so the practical operational path is docker exec into the database container.

Show the host-to-container port mapping

Run this whenever the server is containerized and the host-side connection target matters to scripts, notebooks, or other client tools. It is typically triggered by a new container build, a bind-port change, or a documentation pass that needs the actual workstation entry point. The command runs in Docker, is read-only, and needs only permission to inspect the container. Its purpose is to prove which host port forwards to PostgreSQL’s internal listener.

This command shows how Docker maps the container’s PostgreSQL listener to the Windows host.

docker port stoxx-postgres 5432
0.0.0.0:5434
[::]:5434

The practical workstation target is localhost:5434, not localhost:5432. That distinction must be carried into any future host-side psql, application connection string, or notebook kernel that reaches the container over TCP rather than through docker exec.

Check whether the Windows host already has a native psql client

Run this before writing host-native instructions or assuming psql.exe is directly available in PowerShell. It is usually triggered by workstation onboarding or by the decision to move from container-executed commands to host-executed commands. The command runs in PowerShell, is read-only, and needs no special privileges. Its purpose is to make the local tooling boundary explicit instead of relying on an unverified assumption about PATH.

This command reports whether the Windows host can run psql directly without entering the container first.

if (Get-Command psql -ErrorAction SilentlyContinue) { Get-Command psql | Select-Object Source, Version | Format-Table -AutoSize | Out-String } else { 'psql not found on host PATH' }
psql not found on host PATH

On this workstation the only installed client surface is the one inside stoxx-postgres. That is why the PostgreSQL chapter should currently prefer docker exec stoxx-postgres psql ... for live captures instead of publishing host-native psql commands that would fail immediately on the same machine.

List the databases visible from the current lab client

Run this after client installation or after any restore or rebuild where the cluster contents may have changed. It is typically triggered by basic sanity checking, database creation work, or confirming that the target database exists before attempting a scripted connection. The command runs through psql, is read-only, and needs only a role that can connect to the cluster. Its purpose is to prove which databases the current client can enumerate right now.

This command lists the databases that the current psql client can see in the containerized cluster.

docker exec stoxx-postgres psql -U postgres -lqt
 postgres  | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | 
 stoxx     | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | 
 template0 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | =c/postgres          +
           |          |          |                 |            |            |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | =c/postgres          +
           |          |          |                 |            |            |            |           | postgres=CTc/postgres

The cluster is the expected minimal shape: the working stoxx database, the administrative postgres database, and the two standard templates. That small inventory is useful because later restore and backup notes can reason from a clean cluster baseline rather than from an estate with many unrelated databases.

FlagSyntaxDescription
-lpsql -lList the databases available in the cluster and exit.
-qpsql -qSuppress startup noise and informational chatter.
-tpsql -tReturn tuples only, removing headers and footers from aligned output.

Authentication And Connection

psql connection behavior is a combination of client flags, libpq connection options, server-side pg_hba.conf, and server-side SSL configuration. A connection that “just works” on one host can fail on another because any one of those layers changed, so the safe pattern is to make host, port, database, role, timeout, and TLS expectations explicit.

PostgreSQL | psql | connection strings and current authentication surface

This subsection shows the current lab’s connection surface first and only then uses that surface to demonstrate explicit TCP connection strings. The current environment is intentionally convenient for a disposable lab, but that convenience should be documented as a lab condition rather than mistaken for PostgreSQL’s general default.

Inspect the current listen, socket, and SSL settings

Run this before troubleshooting reachability or publishing any connection instructions. It is typically triggered by a new server build, a port-forwarding change, or confusion about why one client path uses a socket while another uses TCP. The command runs through psql, is read-only, and needs only a valid login path. Its purpose is to surface the server settings that explain which transport paths are even possible.

Output fieldSource expressionUnit / typeMeaning
listen_addressescurrent_setting('listen_addresses')textThe network interfaces on which PostgreSQL accepts TCP connections.
portcurrent_setting('port')textThe server-side listener port.
ssl_enabledcurrent_setting('ssl')textWhether PostgreSQL server-side SSL is enabled.
socket_dirscurrent_setting('unix_socket_directories')textThe directory path used for Unix-domain sockets.

This command shows the active transport surface of the running PostgreSQL server.

docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off --csv -c "SELECT current_setting('listen_addresses') AS listen_addresses, current_setting('port') AS port, current_setting('ssl') AS ssl_enabled, current_setting('unix_socket_directories') AS socket_dirs;"
listen_addresses,port,ssl_enabled,socket_dirs
*,5432,off,/var/run/postgresql

The server is listening on all container interfaces, on port 5432, with Unix-domain sockets in /var/run/postgresql, and server-side SSL is currently disabled. That last fact becomes operationally important as soon as the client sets sslmode=require or anything stricter.

Show why loopback connections work without an interactive password in this lab

Run this when a lab connection succeeds more easily than expected and you need to prove whether the behavior comes from pg_hba.conf rather than from a hidden credential source. It is usually triggered by environment review, authentication debugging, or hardening work that needs a before-and-after baseline. The command runs against a system view, is read-only, and needs enough privileges to inspect server configuration metadata. Its purpose is to reveal the active authentication rules instead of inferring them from client symptoms.

Output fieldSource columnUnit / typeMeaning
line_numberpg_hba_file_rules.line_numberintegerThe line number in pg_hba.conf that produced this rule.
typepg_hba_file_rules.typetextThe rule class such as local or host.
databasepg_hba_file_rules.databasetext[]The database target list covered by the rule.
user_namepg_hba_file_rules.user_nametext[]The role target list covered by the rule.
addresspg_hba_file_rules.addresstextThe client address range covered by a host rule.
auth_methodpg_hba_file_rules.auth_methodtextThe authentication method PostgreSQL will apply if the rule matches.

This command reads the effective pg_hba.conf rules that explain the current lab’s login behavior.

docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules ORDER BY line_number;"
 line_number | type  |   database    | user_name |  address  |  auth_method  
-------------+-------+---------------+-----------+-----------+---------------
         117 | local | {all}         | {all}     |           | trust
         119 | host  | {all}         | {all}     | 127.0.0.1 | trust
         121 | host  | {all}         | {all}     | ::1       | trust
         124 | local | {replication} | {all}     |           | trust
         125 | host  | {replication} | {all}     | 127.0.0.1 | trust
         126 | host  | {replication} | {all}     | ::1       | trust
         128 | host  | {all}         | {all}     | all       | scram-sha-256
(7 rows)

The reason local socket and loopback TCP sessions connect so easily is visible directly in the rules: the lab allows trust for local and loopback paths, while all other network addresses fall through to scram-sha-256. That is a deliberate chapter-lab convenience, not a claim that production PostgreSQL should behave this way.

Connect over TCP with an explicit libpq connection string

Run this when the goal is to make the connection target explicit in automation instead of relying on implicit socket defaults. It is typically triggered by host-to-container access, connection troubleshooting, or any script that should survive changes to PGHOST, PGPORT, or the current OS user name. The command runs through psql, is read-only, and needs a valid login path. Its purpose is to demonstrate the clean libpq pattern for naming host, port, database, role, timeout, and application identity in one place.

Output fieldSource expressionUnit / typeMeaning
application_namecurrent_setting('application_name')textThe client-supplied application identity visible in server views and logs.
client_addrinet_client_addr()inetThe client IP address seen by the server for this session.

This command connects over TCP with an explicit libpq connection string and stamps the session with an application name.

docker exec stoxx-postgres bash -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql "host=127.0.0.1 port=5432 dbname=stoxx user=postgres application_name=psql_note02_connect_demo connect_timeout=3" -P pager=off -c "SELECT current_setting(''"'"'application_name'"'"''"'"') AS application_name, inet_client_addr() AS client_addr;"'
     application_name     | client_addr 
--------------------------+-------------
 psql_note02_connect_demo | 127.0.0.1
(1 row)

The important part is not the query but the connection string shape. host=127.0.0.1 forces TCP instead of the default local socket, connect_timeout=3 bounds how long automation waits for reachability, and application_name makes the session identifiable in activity views and logs.

FlagSyntaxDescription
-hpsql -h 127.0.0.1Connect over TCP to the named host, or use a socket directory if the argument begins with a slash.
-ppsql -p 5432Connect to the named TCP port or socket file extension.
-Upsql -U postgresUse the named PostgreSQL role for login.
-dpsql -d stoxxConnect to the named database.
-wpsql -wNever prompt for a password, which is important for unattended jobs.
-Wpsql -WForce an early password prompt even if the server might not require one.

PostgreSQL | psql | password files and transport security

This subsection covers the client-side security boundary rather than the server-side role model. The lab still allows local trust, but the documented safe pattern needs to explain what happens once a password or TLS requirement enters the picture.

Verify the current TCP connection’s SSL state

Run this when you need proof of whether a TCP client is actually using TLS rather than assuming encryption from the mere fact that a network connection succeeded. It is usually triggered by transport hardening, connection debugging, or a change to sslmode. The command runs through psql, is read-only, and needs a valid login path. Its purpose is to show what the server sees for the current session’s SSL state.

Output fieldSource expressionUnit / typeMeaning
client_addrinet_client_addr()inetThe remote address seen by the server for this session.
client_portinet_client_port()integerThe client-side source port for the TCP session.
using_sslpg_stat_ssl.sslbooleanWhether this backend is using SSL/TLS.

This command opens a TCP session with libpq’s default sslmode=prefer behavior and shows whether the current session actually negotiated SSL.

docker exec stoxx-postgres bash -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql "host=127.0.0.1 port=5432 dbname=stoxx user=postgres sslmode=prefer" -P pager=off -c "SELECT inet_client_addr() AS client_addr, inet_client_port() AS client_port, ssl.ssl AS using_ssl FROM pg_stat_ssl AS ssl WHERE ssl.pid = pg_backend_pid();"'
 client_addr | client_port | using_ssl 
-------------+-------------+-----------
 127.0.0.1   |       36194 | f
(1 row)

The session is definitely TCP because inet_client_addr() and inet_client_port() are populated, but it is not encrypted because the server has ssl = off. That is fully consistent with libpq’s documented default sslmode=prefer: try SSL first, then fall back to plain TCP if SSL is unavailable.

See how sslmode=require fails when server SSL is off

Run this any time the client has been told to require TLS and you want the failure mode to be explicit rather than inferred. It is typically triggered by hardening work, a new certificate rollout, or a suspicion that the client is silently falling back to plaintext. The command runs through psql, is read-only, and needs only the ability to attempt a connection. Its purpose is to show the exact connection failure and exit code that automation will see when TLS is mandatory but unavailable.

This command requires SSL on a server that currently has ssl = off, then prints the resulting process exit code.

docker exec stoxx-postgres bash -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql "host=127.0.0.1 port=5432 dbname=stoxx user=postgres sslmode=require" -c "SELECT 1;"'; Write-Output "EXIT_CODE=$LASTEXITCODE"
EXIT_CODE=2
psql: error: connection to server at "127.0.0.1", port 5432 failed: server does not support SSL, but SSL was required

This is the correct failure. The client did not silently downgrade the transport once sslmode=require was declared. In production, the stronger pattern is usually sslmode=verify-full plus a trusted root CA, because that verifies both encryption and server identity. The current lab cannot demonstrate that yet because the server is intentionally running without SSL.

Script And Variable Execution

psql is both an interactive client and a non-interactive script runner. The right choice depends on whether the work is a one-off probe, a staged file, or a reusable batch with parameter substitution and meta-commands.

PostgreSQL | psql | inline and file execution

This subsection shows the two primary non-interactive execution paths: -c for short diagnostics and -f for saved scripts. The safe boundary is to move into files as soon as the logic deserves version control, repeatability, or line-numbered error reporting.

Execute a one-off diagnostic with -c

Run this when the query is small, self-contained, and unlikely to be reused beyond the current check. It is typically triggered by quick health probes, inventory checks, and simple fact gathering during a troubleshooting session. The command runs through psql, is read-only in this example, and needs only a valid login path. Its purpose is to show the lightest-weight non-interactive execution form.

This command runs a short SQL diagnostic inline and then exits immediately.

docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off -c "SELECT current_database() AS db_name, current_user AS role_name;"
 db_name | role_name 
---------+-----------
 stoxx   | postgres
(1 row)

-c is the right tool when the SQL is short enough to stay readable on one line and the command does not need psql meta-commands. PostgreSQL documentation is explicit that each SQL string passed to -c is sent as one request, which matters when later automation mixes multiple statements or relies on transaction boundaries.

Execute a saved SQL script with -f

Run this when the logic deserves a file boundary, line-numbered errors, or multiple statements that should not live inside one long shell string. It is typically triggered by maintenance scripts, bootstrap routines, and any command that has graduated from ad-hoc diagnostics into repeatable operational work. The command runs through psql, is read-only in this example, and needs only a valid login path. Its purpose is to show the file-driven execution path that later backup, restore, and admin notes will use.

This command executes a saved SQL script with -f and returns both result sets in sequence.

docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off -f /tmp/psql_note02_demo.sql
 database_name | role_name 
---------------+-----------
 stoxx         | postgres
(1 row)
 
 schemaname |     tablename     
------------+-------------------
 bronze     | dim_country
 bronze     | dim_index
 bronze     | eurostoxx50_ohlcv
 bronze     | index_dim
 bronze     | oil20_ohlcv
 bronze     | pulse
(6 rows)

-f is the better long-form boundary because psql can then report line numbers when the script fails, and the SQL itself can live in version control instead of in shell quoting. That is materially safer once the batch is longer than a simple diagnostic query.

FlagSyntaxDescription
-cpsql -c "SELECT ..."Execute one SQL command string or one backslash command and exit.
-fpsql -f script.sqlExecute commands from a file rather than from standard input.
-Xpsql -XSkip ~/.psqlrc, which is safer for deterministic automation.
-qpsql -qSuppress extra chatter for cleaner scripted output.
-Ppsql -P pager=offSet printing options from the command line for non-interactive runs.

PostgreSQL | psql | variables and meta-commands

This subsection covers the features that make psql more than a thin SQL pipe: backslash commands for client-side inspection and \set or -v variables for parameterized batches.

Show current connection metadata with \conninfo

Run this when session context matters more than query output, especially after a reconnect, a host change, or a shift from socket to TCP access. It is typically triggered by connection debugging or by a script that must prove which transport path is currently active. The command runs through psql, is read-only, and needs only a valid login path. Its purpose is to ask the client itself how it is connected instead of guessing from shell context.

This command feeds the \conninfo meta-command into psql and prints the current connection path.

docker exec stoxx-postgres bash -lc "printf '\\conninfo\n' | psql -U postgres -d stoxx -P pager=off"
You are connected to database "stoxx" as user "postgres" via socket in "/var/run/postgresql" at port "5432".

That single line is high-value operationally: it proves the session is using a Unix-domain socket, not TCP, which means client-side sslmode is irrelevant for this particular connection path and loopback pg_hba.conf rules are not what admitted the session.

Parameterize a batch with \set variables

Run this when a short batch needs a reusable parameter without hard-coding the same literal in several places. It is typically triggered by inventory scripts, repeatable demos, and operational commands that should stay human-readable while still accepting a few inputs. The command runs through psql, is read-only in this example, and needs only a valid login path. Its purpose is to demonstrate the client-side variable mechanism that later file-based scripts can reuse.

This command feeds a small parameterized batch into psql, using \set to control the row limit.

@'
\set show_limit 3
SELECT symbol, date, close
FROM silver.stoxxusa50_ohlcv
ORDER BY date DESC, symbol
LIMIT :show_limit;
'@ | docker exec -i stoxx-postgres psql -U postgres -d stoxx -P pager=off
 symbol |    date    | close  
--------+------------+--------
 AAPL   | 2026-04-07 |  253.5
 ABBV   | 2026-04-07 | 206.37
 AMAT   | 2026-04-07 | 354.31
(3 rows)

This is client-side substitution, not server-side prepared-parameter binding. That distinction matters because \set is excellent for small operational scripts, but application code should still use driver parameters rather than string substitution.

FlagSyntaxDescription
-vpsql -v name=valueSet a psql variable from the command line.
-apsql -aEcho all nonempty input lines from a script.
-epsql -eEcho SQL commands sent to the server.
-Epsql -EEcho the SQL generated by backslash inspection commands such as \d.
-Lpsql -L session.logWrite all session output to a log file as well as standard output.

Error Handling And Exit Codes

psql is safe in automation only when its failure boundary is explicit. PostgreSQL documentation defines the key exit-code split clearly: normal success returns 0, fatal client or connection problems return 1 or 2 depending on the case, and script termination caused by ON_ERROR_STOP returns 3.

Exit codeMeaningWhen it appears in practice
0Normal successThe command connected, executed, and did not hit a fatal client-side failure.
1Fatal psql/script failureA non-connection failure stopped the run, such as a failed single-transaction batch.
2Connection failurepsql could not establish the session it was asked to open.
3Script stopped by ON_ERROR_STOPA non-interactive script hit a SQL error and psql was explicitly told to stop immediately.

PostgreSQL | psql | fail-fast behavior

This subsection demonstrates the exact default and fail-fast behaviors that scheduled jobs depend on. The distinction is not academic: a script that continues after an early error can leave the database in a partially applied state while still looking superficially successful from the shell.

See the default continue-after-error behavior

Run this when validating how psql behaves on errors before wiring it into automation. It is typically triggered by the first draft of a script runner, by a production-hardening review, or by a suspicion that a job kept going after an early failure. The command runs through psql, is read-only in the sense that it only executes selects here, and needs only a valid login path. Its purpose is to prove that the default non-interactive behavior is continuation, not fail-fast.

This command sends three statements to psql, with an error in the middle, and then prints the process exit code seen by PowerShell.

@'
SELECT 1 AS before_error;
SELECT 1/0;
SELECT 2 AS after_error;
'@ | docker exec -i stoxx-postgres psql -U postgres -d stoxx -P pager=off; Write-Output "EXIT_CODE=$LASTEXITCODE"
 before_error 
--------------
            1
(1 row)
 
 after_error 
-------------
           2
(1 row)
 
EXIT_CODE=0
ERROR:  division by zero

The middle statement failed, but psql kept processing the rest of the script and the process still exited 0. That is exactly why unattended jobs should not rely on default behavior when partial execution would be unsafe.

Stop immediately on the first SQL error with ON_ERROR_STOP

Run this as soon as a non-interactive script can no longer tolerate partial progress. It is typically triggered by maintenance automation, deployment scripts, or multi-step admin tasks where “continue after error” is operationally wrong. The command runs through psql, is read-only in this example, and needs only a valid login path. Its purpose is to convert SQL errors into immediate script termination with a documented non-zero exit code.

This command repeats the same three-statement batch, but enables ON_ERROR_STOP so the run aborts at the first error.

@'
SELECT 1 AS before_error;
SELECT 1/0;
SELECT 2 AS after_error;
'@ | docker exec -i stoxx-postgres psql -U postgres -d stoxx -v ON_ERROR_STOP=1 -P pager=off; Write-Output "EXIT_CODE=$LASTEXITCODE"
 before_error 
--------------
            1
(1 row)
 
EXIT_CODE=3
ERROR:  division by zero

This is the correct automation boundary. The second query fails, the third query is never executed, and the process exits with 3, which PostgreSQL documentation reserves for ON_ERROR_STOP-driven script termination.

Wrap a non-interactive run in one transaction with -1

Run this when a batch should either apply completely or roll back completely, and when every statement in the batch is valid inside a transaction block. It is typically triggered by bootstrap scripts, schema changes, and small maintenance routines where half-applied state is unacceptable. The command runs through psql, is state-changing by design, and therefore belongs only in controlled admin workflows. Its purpose is to demonstrate how -1 gives a file-driven run an all-or-nothing boundary.

This command starts a single-transaction batch that creates a demo table, inserts one row, then deliberately violates the primary key and verifies that the whole batch rolled back.

docker exec stoxx-postgres psql -U postgres -d stoxx -v ON_ERROR_STOP=1 -1 -c "DROP TABLE IF EXISTS demo_stc.psql_tx_demo; CREATE TABLE demo_stc.psql_tx_demo(id int primary key); INSERT INTO demo_stc.psql_tx_demo VALUES (1); INSERT INTO demo_stc.psql_tx_demo VALUES (1);"; Write-Output "EXIT_CODE=$LASTEXITCODE"; docker exec stoxx-postgres psql -U postgres -d stoxx -P pager=off -c "SELECT to_regclass('demo_stc.psql_tx_demo') AS regclass_name, COUNT(*) FILTER (WHERE table_schema = 'demo_stc' AND table_name = 'psql_tx_demo') AS table_exists FROM information_schema.tables WHERE table_schema = 'demo_stc' AND table_name = 'psql_tx_demo';"
DROP TABLE
CREATE TABLE
INSERT 0 1
EXIT_CODE=1
 regclass_name | table_exists 
---------------+--------------
               |            0
(1 row)
 
NOTICE:  table "psql_tx_demo" does not exist, skipping
ERROR:  duplicate key value violates unique constraint "psql_tx_demo_pkey"
DETAIL:  Key (id)=(1) already exists.

Even though the table was created and the first insert succeeded temporarily, the duplicate-key failure caused the entire -1 batch to roll back. The follow-up query proves that no table remains. That is the right behavior for schema and data-change bundles that should never land partially.

FlagSyntaxDescription
-v ON_ERROR_STOP=1psql -v ON_ERROR_STOP=1Stop non-interactive processing immediately on the first SQL error.
-1psql -1 -f script.sqlWrap all -c and -f work in one explicit transaction.
-spsql -sPrompt before each command, which is useful for debugging scripts interactively.
-bpsql -bEcho failed SQL commands to standard error.

Output Shaping

The same result set can be right or wrong depending on the consumer. Human readers usually want aligned output or expanded mode; shell pipelines want delimiter-separated rows without headers or footers; file-oriented automation often wants CSV.

PostgreSQL | psql | delimited and machine-readable output

This subsection covers the output modes most likely to show up in automation, notebook capture, and quick shell pipelines.

Emit CSV output

Run this when the result set is meant to flow into a file, a parser, or another tool that already expects comma-separated values. It is typically triggered by export steps, notebook ingestion, or quick comparisons against spreadsheet-style outputs. The command runs through psql, is read-only here, and needs only a valid login path. Its purpose is to show the cleanest built-in machine-readable export format that psql offers without custom post-processing.

This command emits the latest three rows from silver.stoxxusa50_ohlcv in CSV form.

docker exec stoxx-postgres bash -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql -w "host=127.0.0.1 port=5432 dbname=stoxx user=postgres application_name=psql_csv_demo" --csv -c "SELECT symbol, date, close FROM silver.stoxxusa50_ohlcv ORDER BY date DESC, symbol LIMIT 3;"'
symbol,date,close
AAPL,2026-04-07,253.5
ABBV,2026-04-07,206.37
AMAT,2026-04-07,354.31

--csv is the lowest-friction option when the downstream consumer truly wants CSV. It is better than hand-assembling delimiters because quoting rules, commas inside values, and header handling stay delegated to the client.

Emit delimiter-separated rows without headers or footers

Run this when the output is meant for a shell pipeline rather than for a spreadsheet or a human reader. It is typically triggered by quick inspection, xargs-style processing, or scripts that want rows in a compact predictable form. The command runs through psql, is read-only here, and needs only a valid login path. Its purpose is to show the classic -A plus custom separator pattern.

This command emits three rows in unaligned, pipe-separated, tuples-only form.

docker exec stoxx-postgres psql -U postgres -d stoxx -A -F '|' -t -c "SELECT symbol, date, close FROM silver.stoxxusa50_ohlcv ORDER BY date DESC, symbol LIMIT 3;"
AAPL|2026-04-07|253.5
ABBV|2026-04-07|206.37
AMAT|2026-04-07|354.31

This is the right shape when a shell or another program is the next consumer. -A removes alignment padding, -F sets the delimiter, and -t removes headers and footers so the output is just row payload.

FlagSyntaxDescription
--csvpsql --csvEmit CSV output instead of aligned tables.
-Apsql -ASwitch to unaligned output mode.
-F`psql -F ''`
-Rpsql -R $'\0'Set the record separator for unaligned output.
-tpsql -tSuppress headers and row-count footers.

This subsection covers the human-reader modes that become useful once aligned tables start getting too wide or when the default footer is more noise than signal.

Use expanded mode for wide rows

Run this when a row has enough columns that aligned-table output is harder to read than a record-per-block layout. It is typically triggered by inspection of configuration rows, wide metadata output, or a single-row diagnostic result that needs visual clarity more than dense tabular formatting. The command runs through psql, is read-only, and needs only a valid login path. Its purpose is to show the built-in wide-row format instead of relying on manual reformatting.

This command prints a single OHLCV row in expanded mode.

docker exec stoxx-postgres psql -U postgres -d stoxx -x -P pager=off -c "SELECT symbol, date, close, volume FROM silver.stoxxusa50_ohlcv ORDER BY date DESC, symbol LIMIT 1;"
-[ RECORD 1 ]------
symbol | AAPL
date   | 2026-04-07
close  | 253.5
volume | 60820961

Expanded mode is better than horizontal scrolling once the row is conceptually “one object” rather than “many comparable rows”. It is especially useful in admin notes where one backend, one setting row, or one lock row is being inspected closely.

Run this when you still want aligned tables but do not want the (n rows) footer mixed into downstream capture or note rendering. It is typically triggered by documentation capture, markdown conversion, or a short aligned output that looks cleaner without the final count line. The command runs through psql, is read-only, and needs only a valid login path. Its purpose is to show that psql footer control does not require abandoning aligned output entirely.

This command keeps aligned formatting but removes the default row-count footer.

docker exec stoxx-postgres bash -lc 'export PGPASSWORD="$POSTGRES_PASSWORD"; psql -w "host=127.0.0.1 port=5432 dbname=stoxx user=postgres" -P footer=off -c "SELECT schemaname, tablename FROM pg_tables WHERE schemaname = ''gold'' ORDER BY tablename;"'
 schemaname |     tablename     
------------+-------------------
 gold       | index_performance
 gold       | scores_daily
 gold       | scores_quarterly

This is a good compromise when humans still need to read the result directly but the extra footer line would create cleanup work for the next tool or the next markdown capture step.

FlagSyntaxDescription
-xpsql -xTurn on expanded output mode.
-P footer=offpsql -P footer=offSuppress the default row-count footer while keeping the chosen output format.
-Hpsql -HEmit HTML table output.
-opsql -o result.txtWrite query output to a file or pipe.

Local Admin Access

SQL Server has a Dedicated Admin Connection. PostgreSQL does not have an exact DAC equivalent. The nearest operational alternatives are local OS-user access to the running cluster and offline single-user mode when the postmaster is not running and catalog-level recovery work is required.

PostgreSQL | local admin | container and recovery paths

This subsection documents the two break-glass patterns that matter in this chapter’s Docker lab: enter the container as the postgres OS user and inspect the live cluster, or use postgres --single only when the cluster is intentionally offline.

Check server status from inside the container as the postgres OS user

Run this when the normal SQL session path is not the question and the actual question is whether the postmaster is alive. It is typically triggered by a startup failure, a health-check discrepancy, or the need to prove the live data directory and process state before doing anything more invasive. The command runs in the container shell, is read-only, and must run as the unprivileged postgres OS user because PostgreSQL server-control tools refuse root. Its purpose is to verify that the postmaster is running from the server side rather than from a client symptom.

This command uses pg_ctl status inside the container as the postgres OS user.

docker exec -u postgres stoxx-postgres bash -lc "/usr/lib/postgresql/16/bin/pg_ctl -D /var/lib/postgresql/data status"
pg_ctl: server is running (PID: 1)
/usr/lib/postgresql/16/bin/postgres

That output proves the cluster is alive and identifies the postmaster binary currently serving the data directory. It is the PostgreSQL equivalent of checking the actual engine state before trusting any higher-level symptom.

See why single-user mode is an offline recovery path

Run this only when documenting or rehearsing disaster-recovery procedure boundaries. It is typically triggered by catalog-repair planning, not by ordinary troubleshooting, because single-user mode is not meant to coexist with a running multi-user postmaster. The command is state-capable in general, though this example only attempts a read. It must run as the postgres OS user and must target the data directory directly. Its purpose is to show the guardrail that prevents operators from treating single-user mode as a second normal connection path.

This command attempts to start PostgreSQL in single-user mode while the normal postmaster is still running.

docker exec -u postgres stoxx-postgres bash -lc "printf 'SELECT current_database();\n' | /usr/lib/postgresql/16/bin/postgres --single -D /var/lib/postgresql/data stoxx"
2026-04-18 21:44:24.612 UTC [4559] FATAL:  lock file "postmaster.pid" already exists
2026-04-18 21:44:24.612 UTC [4559] HINT:  Is another postmaster (PID 1) running in data directory "/var/lib/postgresql/data"?

This is the correct refusal. Single-user mode is an offline recovery tool. If the normal postmaster is already serving the cluster, PostgreSQL blocks the second startup by design. In real recovery work the multi-user server is stopped first, and only then is single-user mode considered.

FlagSyntaxDescription
docker exec -u postgresdocker exec -u postgres ...Run the command as the container’s postgres OS user instead of as root.
pg_ctl -Dpg_ctl -D /var/lib/postgresql/data statusPoint pg_ctl at the target data directory.
postgres --singlepostgres --single -D ... stoxxStart PostgreSQL in single-user mode against one database.
postgres -Dpostgres --single -D /var/lib/postgresql/data stoxxSpecify the cluster data directory for direct server startup.

psql Flag Reference

This final section is the compact lookup surface once the behavior above is understood.

Login and connection

FlagSyntaxDescription
-dpsql -d stoxxConnect to the named database.
-hpsql -h 127.0.0.1Connect to the named host, or to a socket directory if the value begins with a slash.
-ppsql -p 5432Connect to the named TCP port or socket file extension.
-Upsql -U postgresUse the named PostgreSQL role for login.
-wpsql -wNever prompt for a password.
-Wpsql -WForce an early password prompt.
-Xpsql -XDo not read ~/.psqlrc, which is safer for deterministic automation.

Execution and scripting

FlagSyntaxDescription
-cpsql -c "SELECT 1"Execute one SQL string or one backslash command and exit.
-fpsql -f script.sqlExecute commands from a file.
-vpsql -v name=valueSet a psql variable from the command line.
-1psql -1 -f script.sqlWrap all -c and -f work in one transaction.
-apsql -aEcho all nonempty input lines from the script.
-epsql -eEcho SQL sent to the server.
-Epsql -EEcho the SQL generated by internal backslash commands.
-spsql -sPrompt before each query in single-step mode.
-Spsql -SEnd SQL commands at newline instead of semicolon.

Output formatting

FlagSyntaxDescription
--csvpsql --csvEmit CSV output.
-Apsql -AUse unaligned output mode.
-F`psql -F ''`
-Rpsql -R '\0'Set the record separator for unaligned output.
-tpsql -tSuppress headers and footers.
-xpsql -xTurn on expanded output.
-Ppsql -P footer=offSet a \pset printing option such as footer control.
-opsql -o result.txtWrite output to a file or pipe.
-Lpsql -L session.logCopy all session output to a log file.
-qpsql -qRun quietly with less client chatter.