Self-host pglens on a server
pglens normally runs on your laptop. You can also run it on a server and open it at an address like https://db.example.com, so you and your team reach your databases from any browser without installing anything.
You need a server with Docker, a domain you control, and about ten minutes. pglens sits behind a reverse proxy that handles HTTPS and an extra login.
Requires pglens 3.7 or later. Updated October 27, 2026.
1. Run the container
On the server, start the published image. Replace db.example.com with your subdomain.
docker run -d --name pglens --restart unless-stopped \
-p 127.0.0.1:54321:54321 \
-v pglens-data:/home/node/.pglens \
-e PGLENS_PUBLIC_URL=https://db.example.com \
ghcr.io/tsvillain/pglens:latest
Images for linux/amd64 and linux/arm64 are published with every release, tagged latest, 3.7 and 3.7.0. Pin a version in production. To build it yourself, run docker build -t pglens . in a clone of the repository.
-p 127.0.0.1:54321:54321 publishes the port on the server's loopback only, so the proxy can reach pglens and the internet can't. The pglens-data volume keeps saved connections, passwords, query history and the sign-in token across restarts and updates.
2. Put HTTPS in front
Point a DNS record for your subdomain at the server, then configure one of these proxies.
Caddy
Caddy gets a certificate automatically. Generate the password hash with caddy hash-password.
db.example.com {
basic_auth {
admin <password hash>
}
reverse_proxy 127.0.0.1:54321
}
nginx
server {
listen 443 ssl;
server_name db.example.com;
# ssl_certificate / ssl_certificate_key ...
auth_basic "pglens";
auth_basic_user_file /etc/nginx/pglens.htpasswd;
location / {
proxy_pass http://127.0.0.1:54321;
proxy_set_header Host $host;
}
}
The proxy must pass the original Host header. Caddy does this by default; nginx needs the proxy_set_header line. pglens answers 403 to any hostname other than the one in PGLENS_PUBLIC_URL, which blocks DNS-rebinding attacks.
3. Sign in
docker logs pglens
# ✓ Server running on https://db.example.com
# Open: https://db.example.com/?token=...
Open that link once. pglens stores the token in an HttpOnly, Secure, SameSite=Strict cookie and removes it from the address bar. To print the token again, run docker exec pglens node bin/pglens token.
Settings
| Variable | Default | What it does |
|---|---|---|
PGLENS_PUBLIC_URL |
unset | Public origin, such as https://db.example.com. Allows that hostname, marks the cookie Secure for https, and is the URL pglens prints. Subdomains only; a path like /pglens is rejected. |
PGLENS_BIND |
127.0.0.1, 0.0.0.0 in Docker |
Address the server listens on. |
PGLENS_PORT |
54321, or a random port if busy |
Fixed port. If it's taken, pglens exits instead of picking another one. |
PGLENS_SECRET_STORE |
keychain, file in Docker |
file keeps passwords in ~/.pglens/secrets.json (mode 0600) on servers without a keychain. |
PGLENS_NO_UPDATE_CHECK |
unset, 1 in Docker |
Turns off the daily update check. |
Without Docker
Install pglens with the usual command, then run pglens serve in the foreground under systemd or another process manager. Leave PGLENS_BIND at 127.0.0.1 when the proxy runs on the same machine.
# /etc/systemd/system/pglens.service
[Unit]
Description=pglens
After=network.target
[Service]
User=pglens
Environment=PGLENS_PUBLIC_URL=https://db.example.com
Environment=PGLENS_PORT=54321
Environment=PGLENS_SECRET_STORE=file
ExecStart=/home/pglens/.pglens/bin/pglens serve
Restart=on-failure
[Install]
WantedBy=multi-user.target
Access and security
- One token gives full access. Anyone with the token can open every saved connection and run anything those database users can run. There are no per-user accounts or audit log, so keep the proxy's own login in front: basic auth as above, Cloudflare Access, Tailscale or an OAuth proxy.
- Use least-privilege database users. Connect with a read-only role wherever editing isn't needed.
- Rotate the token to sign everyone out:
docker exec pglens rm /home/node/.pglens/token && docker restart pglens - Always use HTTPS. Over plain HTTP the token travels in clear text.
- Hostnames resolve from the server. Inside the container,
localhostmeans the container itself, so connect to databases by their network address. The same applies to AI mode's Ollama host.
Update
docker pull ghcr.io/tsvillain/pglens:latest && docker rm -f pglens
# run the same docker run command again; the volume keeps your data