Skip to content

Repository files navigation

pgtower logo

pgtower

The control tower for your Postgres fleet.

pgtower.dev · formerly pgtui — what changed

CI Release License: MIT

A keyboard-first PostgreSQL administration TUI for sysadmins — think k9s, but for Postgres. A control tower sees every aircraft, spots conflicts before they happen and decides who goes next; pgtower does that for your Postgres servers. Where most database TUIs are data browsers, pgtower leans into operations: watch and kill sessions, manage roles and grants, create/drop databases, and inspect cluster health — all with strong guards against destructive mistakes.

Single static binary, no dependencies, no container required.

pgtower demo: dashboard, sessions, blocking tree, roles, databases, switching servers and the tuning advisor

Recorded with VHS against throwaway clusters — regenerate with vhs docs/demo/demo.tape.

Why pgtower?

Tools like pgcli, lazysql and rainfrog are great for browsing data and running queries. pgtower overlaps there (it has a query runner and a read-only data browser), but its focus is cluster administration:

  • Sessions — see every client backend and pg_cancel_backend / pg_terminate_backend a runaway one.
  • Roles — create users, edit attributes, grant and revoke privileges on databases, and drop roles (including a safe force-drop that reassigns ownership instead of deleting data).
  • Databases — create and drop databases, browse tables and sizes, and get a \d-style structure view.
  • Locks — a blocking tree showing who is waiting on whom.
  • Dashboard — connections vs max_connections, cache hit ratio, uptime, replication, longest active query, plus a connection advisor that flags when you're near the limit or drowning in idle connections.
  • Tuning — a read-only settings advisor, an ALTER SYSTEM editor, and a pg_hba viewer/editor with a lockout-proof safety net.
  • Servers — keep several clusters in one place and hop between them with S; test reachability, tag production servers, and get a plain-language diagnosis when one can't be reached.

Core management (roles, databases, sessions, queries) works without a superuser — a role with CREATEROLE/CREATEDB is enough. The Tuning tab's ALTER SYSTEM and pg_hba editing are the exception and require a superuser (see Permissions).

Features

Tab What it does
Servers (S) Connection manager: list, switch, add / edit / delete, t test (latency + server version), * set the default. Tag servers dev / staging / prod (prod gets a red header badge). Connection failures are explained — not responding, refused, no route, auth failed, pg_hba rejected, TLS … — with what to check next, instead of a raw driver error.
1 · Dashboard Cluster health: connections vs max_connections (with reserved slots), cache hit ratio, uptime, total size, commits/rollbacks, version, longest active query, replication. A connection advisor flags near-limit/idle-dominated/idle-in-transaction situations and tells you what to do. Auto-refresh.
2 · Databases Databases (owner, size, connections) → tables → read-only data browser (horizontal column scroll ←→, per-column search /, top query bar e). Create (n) / drop (D) databases, d for a table's structure (columns, indexes, constraints), and / for a fuzzy quick-find in the database/table list.
3 · Query SQL editor with a paged result grid. x runs EXPLAIN (plan only). Writes require confirmation; destructive statements (DROP/TRUNCATE/DELETE/UPDATE without WHERE) require typing yes.
4 · Locks Blocking tree: which session waits on which.
5 · Sessions pg_stat_activity with state/wait/duration/query. c cancels the query, k terminates the connection, / fuzzy quick-find (by PID, user, database, state or query text).
6 · Roles Roles with their attributes and connection limit (CONN, ∞ = unlimited). SUPER and BYPASSRLS are marked ⚠ yes — both ignore row-level security. enter manage the selected role (reset password — random 32-char, shown once; set the connection limit; edit attributes: LOGIN, CREATEDB, CREATEROLE, SUPERUSER, REPLICATION, BYPASSRLS; or show access — a per-database report of what the role owns and is granted, schema/table privileges included). n create, g grant / R revoke (pick the database in a fuzzy finder, then the privilege, then confirm the exact SQL), D drop, F force-drop (reassign ownership to a successor, then drop — no data loss), / fuzzy quick-find by name.
7 · Tuning Config sections (switch with a / s / h): a read-only configuration advisor (shared_buffers, effective_cache_size, work_mem, maintenance_work_mem, max_connections — current vs recommended with a verdict; concrete targets need PGTOWER_HOST_RAM_MB / PGTOWER_HOST_CPUS); an ALTER SYSTEM editor (enter edit / x reset any GUC — validated against type & bounds, applied with pg_reload_conf, / filters, restart-required settings are flagged); and a pg_hba editor (pg_hba_file_rules with parse-error flags; n/e/d add/edit/delete a rule — superuser only, each write is backed up, validated, reloaded and auto-rolled-back if admin login breaks). r refresh.

Install

One line (Linux / macOS)

curl -fsSL https://pgtower.dev/install | sh

The same script straight from GitHub, if you prefer: curl -fsSL https://raw.githubusercontent.com/9level/pgtower/master/install.sh | sh

It detects your OS/arch, downloads the latest static binary (verifying its SHA-256) and installs it to /usr/local/bin — no compiler, no runtime dependencies. The binary runs on any Linux distro (Debian, Ubuntu, Alpine, …) and macOS; only the CPU architecture matters:

amd64 (x86_64) arm64 (aarch64)
Linux ✅ ✅
macOS ✅ ✅
Windows build from source / WSL —

Tweak the install: PGTOWER_INSTALL_DIR="$HOME/.local/bin" or PGTOWER_VERSION=vX.Y.Z. Prefer to read before you pipe to a shell? It's just install.sh. Prebuilt binaries are also attached to each GitHub Release.

From source (Go 1.26+)

git clone https://github.com/9level/pgtower.git
cd pgtower
make build          # -> ./pgtower
make install        # -> /usr/local/bin/pgtower (sudo)

Get running in 30 seconds

pgtower        # first run: the Servers screen opens — press a to add a server

Or, one-off without saving anything:

DATABASE_URL='postgres://user:pass@host:5432/postgres?sslmode=disable' pgtower

Press S any time to switch servers, ? for shortcuts, q to quit.

Upgrading

pgtower checks GitHub for a newer release on startup and, if there is one, asks what to do:

  • Update now — downloads the right binary for your OS/arch, verifies its SHA-256 and replaces the running binary in place. If the install directory needs root (e.g. /usr/local/bin), it shows the exact command to finish. Restart pgtower afterwards.
  • Not now — dismiss for this run.
  • Never suggest again — stop asking for good (a marker in your config dir); re-enable with update_check: true in config.yml.

Disable the check entirely with update_check: false (config.yml) or PGTOWER_UPDATE_CHECK=0. Source/dev builds are never nagged.

Lazy upgrade (one line)

Don't want the prompt at all? Just re-run the installer — it always grabs the latest release, verifies the checksum and replaces your binary (your config.yml is left untouched):

curl -fsSL https://pgtower.dev/install | sh

Pin a version with PGTOWER_VERSION=vX.Y.Z. Installed from source instead? cd pgtower && git pull && make install.

Configuration

pgtower keeps its servers and settings in config.yml, which it manages itself: the Servers screen (S) adds, edits and removes servers and saves them there (mode 0600, since it may hold passwords). The installer creates /opt/pgtower/config.yml; pgtower also looks next to the binary, in ~/.config/pgtower/ and in the working directory (full reference: config.yml.example).

# /opt/pgtower/config.yml
version: 2
default: prod                   # opened at startup (pgtower -s NAME picks another)
connections:
  - name: prod
    url: postgres://admin:secret@10.0.0.5:5432/postgres?sslmode=require
    tag: prod                   # dev | staging | prod
  - name: local
    host: /var/run/postgresql   # host, IP, or a unix-socket directory
    user: postgres
    tag: dev
# refresh_seconds: 5
# scram_iterations: 15000       # PBKDF2 rounds for password-reset hashing
# host_ram_mb: 8192             # host RAM for the Tuning advisor (also per server)
# host_cpus: 4                  # host cores for the Tuning advisor (also per server)
# update_check: true            # startup "newer release available" prompt
pgtower --list          # show the configured servers
pgtower -s local        # open a specific one
  • Environment variables still work and win: DATABASE_URL (or the standard PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE/PGSSLMODE) adds a session-only server named env that opens first and is never written to config.yml. PGTOWER_* variables override the settings.
  • Passwords can stay out of the file: leave the field empty and use ~/.pgpass, or set password_env: SOME_VAR on the server.
  • The admin db (the database in the URL, postgres by default) is where cluster-level queries run (pg_stat_activity, pg_database, replication, locks). pgtower opens additional connections on demand when you browse another database or run a query against a different target (switch it with /).
  • Search locations: PGTOWER_CONFIG (an explicit file) or PGTOWER_CONFIG_DIR (a directory) replace the search; otherwise ./, the binary's directory, ~/.config/pgtower/, /opt/pgtower/, /etc/pgtower/ (first hit wins).

Coming from pgtui

pgtower was called pgtui up to v0.9 (renamed in v0.10 because another project already used that name). Same code, same keys, same config format — only the name changed, and the upgrade handles it for you:

Before (pgtui) Now (pgtower) How it moves
pgtui command pgtower The in-app update or the installer renames the binary; pgtui stays as a symlink so scripts keep working (delete it whenever you like).
/opt/pgtui/, ~/.config/pgtui/, /etc/pgtui/ /opt/pgtower/, ~/.config/pgtower/, /etc/pgtower/ Moved on first run; nothing is left behind. If a directory can't be moved (permissions), it is still read in place and pgtower tells you.
PGTUI_* variables PGTOWER_* The old names are still read as a fallback; pgtower lists the ones you should rename.
application_name = 'pgtui' 'pgtower' Update any monitoring filter that relied on it.
github.com/9level/pgtui github.com/9level/pgtower GitHub redirects the old URLs.

A one-time Welcome to pgtower notice summarises what was done on your machine.

Upgrading from v0.8 or older

Older versions supported a single connection (database_url / host / … at the top of config.yml, and up to v0.7 a .env file). On the first run of a newer pgtower this is converted automatically: the connection becomes a named server (named after its host, set as default), the original files are kept as config.yml.v1.bak / .env.v1.bak (mode 0600), and a one-time notice says what was done. Nothing else is needed; delete the .v1.bak files once you no longer plan to downgrade.

Permissions

  • Read access to pg_stat_* / pg_database is enough for the read-only tabs. To see other sessions' query text on the dashboard/sessions view, connect as a superuser or a member of pg_monitor.
  • Management actions need the usual Postgres privileges: CREATEROLE to create/drop roles, CREATEDB to create databases, and ownership/WITH ADMIN to grant. No superuser required.
  • The Tuning tab is the exception. The settings advisor works for any role, but pg_hba_file_rules (the pg_hba viewer) is superuser-only, ALTER SYSTEM needs a superuser, and editing pg_hba.conf needs a superuser (it writes the file via COPY … TO PROGRAM). Without those, the affected sections show as read-only.

Keyboard shortcuts

Press ? in the app for the full, scrollable list.

Key Action
1–7 switch tab
tab / shift+tab next / previous tab
S / ctrl+o servers: switch, add (a), edit (e), delete (d), test (t), default (*)
? help (all shortcuts)
q / ctrl+c quit
Databases
enter database → tables → read-only data
/ fuzzy quick-find in the database / table list
d describe table (columns, types, indexes, constraints)
n / D create / drop database (drop asks for the name)
Table data
←/→ h/l move between columns (horizontal scroll)
/ search the active column (ILIKE '%term%')
e edit the top query bar (read-only)
Query
i / enter focus the SQL editor
/ or ctrl+t switch the target database (filterable list)
x EXPLAIN (plan, without executing)
ctrl+r / f5 run
Sessions
/ fuzzy quick-find (PID, user, database, state, query)
c cancel the session's query (pg_cancel_backend)
k terminate the connection (pg_terminate_backend)
Roles
/ fuzzy quick-find a role by name
enter manage role: reset password / connection limit / edit attributes / show access
n create role/user
g grant to a database — fuzzy-pick the database, choose the privilege (CONNECT / ALL / read-only / read-write / public schema / owner), then confirm the SQL
R revoke from a database (same picker; undoes the default privileges too)
D drop role (asks for the name)
F force-drop: reassign ownership to a successor, then drop — no data loss
Tuning
a / s / h switch section: advisor / settings / pg_hba
enter / x settings: edit (ALTER SYSTEM) / reset a GUC (/ filters)
n / e / d pg_hba: add / edit / delete a rule (superuser)
r re-read pg_settings / pg_hba

Safety model

pgtower is built so you can't lose data by accident:

  • The query runner classifies every statement: read-only runs immediately, writes confirm with y, and critical statements (DROP DATABASE / DROP TABLE / TRUNCATE, or DELETE/UPDATE without a WHERE) require typing yes.
  • Dropping a role or a database opens a confirmation that only proceeds when you type the object's exact name.
  • Force-drop never deletes data: it REASSIGN OWNED / ALTER DATABASE OWNER to a successor role and revokes privileges before DROP ROLE.
  • Passwords are hashed client-side. Both create role and reset password compute the SCRAM-SHA-256 verifier locally and send only that in CREATE/ALTER ROLE, so the plaintext never reaches the server or its logs. Reset generates a random 32-char password (letters and digits only, safe in any terminal) and shows it once, on screen. A non-ASCII typed password is sent as-is so the server can SASLprep it correctly. The PBKDF2 round count defaults to 15000 (stronger than Postgres' 4096) and is configurable via PGTOWER_SCRAM_ITERATIONS.
  • Editing pg_hba.conf (Tuning tab) never leaves you locked out: pgtower backs up the file, writes the change, checks pg_hba_file_rules for parse errors, reloads, then opens a fresh admin connection to confirm login still works — any failure restores the backup and reloads. It needs a superuser connection and writes via COPY … TO PROGRAM (no adminpack needed).
  • Admin statements run over the pgx simple protocol (required for CREATE/DROP DATABASE), with quoted identifiers.
  • There is no code path that drops a database on its own.

Development

make test     # unit + integration (integration self-skips without DATABASE_URL)
make vet
gofmt -l .    # should be empty

Need a throwaway cluster for the integration tests? A docker-compose.yml (PostgreSQL 18, superuser) ships with the repo:

docker compose up -d
export DATABASE_URL='postgres://postgres:pgtower_test@127.0.0.1:5432/postgres?sslmode=disable'
make test                                   # now runs the live tests too
PGTOWER_HBA_LIVE_TEST=1 go test ./...          # also the guarded pg_hba write test

See CONTRIBUTING.md for the full workflow. To debug the UI, log to a file: PGTOWER_DEBUG=/tmp/pgtower.log ./pgtower.

Distribution & releases

make build-all VERSION=vX.Y.Z   # cross-compile to dist/ (linux/darwin, amd64/arm64)
make release VERSION=vX.Y.Z     # validate semver + clean tree, tag, push

Pushing a v*.*.* tag runs the CI (.github/workflows/ci.yml): tests, then build-all, then the binaries are attached to a GitHub Release.

License & trademarks

The source code is licensed under the MIT License © 2026 9Level.

The MIT license covers the code only — it does not grant rights to the project's brand. "pgtower", "9Level", and the 9Level logo are trademarks of 9Level; see TRADEMARKS.md. If you fork it, please use a different name.

· 9level.dev

About

The control tower for your Postgres fleet — a keyboard-first PostgreSQL administration TUI (k9s for Postgres): sessions, locks, roles & grants, pg_hba, tuning, multiple servers. Single static binary. Formerly pgtui.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages