DrillBit: Stop SSH-ing Into Machines to Touch Your Databases
Table of Contents

The Annoying Thing #
Okay, fine, we’re still SSH-ing into machines. But we’re not doing it anymore. The computer is doing it. That’s the whole point.
I like running isolated Postgres instances. Every app gets its own, in its own Docker container, on whatever server makes sense. It keeps things clean — I can move an app between machines without worrying about shared database state, and one app’s data never accidentally bleeds into another’s. I’ve been doing this for years and it works great.
What doesn’t work great is actually using those databases.
The workflow goes something like: SSH into a machine. Try to remember the container name. Was it myapp_db_1 or myapp-db-1? Run docker exec -it whatever psql -U postgres. Discover I actually need -d myappdb because the database isn’t named postgres. Do my query in raw psql, which is fine but I have perfectly good tools on my laptop that I’d rather be using. Close the session. Repeat for a different database on a different server.
I have TablePlus sitting right there (seriously, if you work with databases and you haven’t tried TablePlus, go fix that). I have pgcli. I have DataGrip if I’m feeling fancy. But getting a connection from my laptop into a Postgres container on a remote server means setting up SSH port forwarding, picking a local port, and somehow keeping track of which port goes where across a dozen databases on several hosts. It’s one of those things that’s just tedious enough that you never get around to properly solving it, so you keep doing the dumb manual thing.
I’ve been annoyed by this for a long time, but I could never justify spending days building a proper tool just to make my own database management slightly less tedious. Then Claude Code happened. I vibe coded the whole thing — described what I wanted, iterated on the design, and ended up with something way more polished than I would have built if I’d been writing every line by hand. The kind of project that only exists because the cost of building it dropped far enough to make it worth doing for an audience of one.
What DrillBit Does #
DrillBit connects to your remote hosts over SSH, finds every Postgres container running on them (including PostGIS and TimescaleDB), pulls credentials out of the container’s environment variables, and sets up local port-forwarded tunnels. You point your database tool at localhost:whatever and you’re in. It greets you with a random tagline each time — things like “your DBA doesn’t need to know” and “127.0.0.1 sweet 127.0.0.1” — because if you’re going to stare at a terminal you might as well be entertained.
To be clear about scope: this is specifically for Postgres running in Docker containers on machines you can SSH into. It’s not for RDS, it’s not for bare-metal Postgres, and it’s not for MySQL or anything else. If your setup looks like mine — a bunch of Docker Compose stacks across a few servers — this is your tool. If it doesn’t, this probably isn’t.

The trick that makes this actually useful instead of just a fancy wrapper around ssh -L is deterministic ports. DrillBit hashes the host name and container name (FNV-1a) to produce a port number in the 10000-65535 range. Same database, same port, every time.
This is my favourite part of the whole thing, and honestly the reason it works as well as it does. Every database gets exactly one local port, permanently. When I set up a connection in TablePlus to localhost:38291 for my production app database, that connection keeps working tomorrow, next week, after a reboot. I never wonder “wait, which port was that one again?” I never accidentally connect to staging when I meant prod. I never run a migration against the wrong tenant because I reused a port number from last time. The port is the identity. One container, one port, one connection string, forever. You set it up once in your database tool and it just stays correct. No more connections that sometimes point to prod, sometimes test, sometimes who-knows-what because you forgot to restart a tunnel.
Under the Hood #
The discovery process is pretty straightforward. DrillBit SSHes into each host and runs docker ps filtered for images matching postgres, postgis, or timescale. For each hit, it runs docker inspect to grab POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB from the container’s environment. All hosts are scanned in parallel, so even with a handful of servers it only takes a couple seconds.
The SSH connections use Go’s golang.org/x/crypto/ssh directly — no shelling out, no subprocess management, no dependency on the host system’s ssh binary at all. The whole thing is a single native Go binary. It reads your ~/.ssh/config and keys, but the SSH implementation itself is pure Go. This means it works on Mac, it works on Linux, and it probably works on Windows though I’ve never tried and don’t particularly care to find out.
One SSH connection per host, shared across all tunnels to that host, reference-counted so it cleans up when the last tunnel disconnects. Each tunnel gets a goroutine that watches for failures and handles reconnection. There are keepalive pings every 30 seconds to catch dead connections early instead of waiting for a query to time out.
Host key verification is Trust-on-First-Use (same as regular SSH), and it’ll yell at you if a key changes.
The progress output during discovery has a vaguely military-briefing aesthetic that I’m not going to pretend was an accident:
[CONN] Establishing link to prod-server-1...
[ OK ] prod-server-1 — secure channel open
[SCAN] prod-server-1 — interrogating docker daemon...
[FIND] prod-server-1 — 3 targets acquired
prod-server-1/app_db ← postgres
prod-server-1/geo_db ← postgis
prod-server-1/api_db ← timescaledb
Using It #
The TUI is BubbleTea. You get a table of every database DrillBit found, with environment labels, the Postgres variant, host, container, credentials, and the assigned port. Hit Space on a row to bring the tunnel up. Hit Enter to bring it up and launch pgcli (or psql, if that’s what you’ve got). Hit y to copy the password or a full connection string to your clipboard (it auto-clears after 30 seconds, because I’m slightly paranoid).
There’s fuzzy search with /, inline credential editing with c if the auto-detected creds are wrong, and you can mark databases with a to auto-connect on startup. That last one is the bit I use most — my daily-driver databases just come up when I launch DrillBit. My database tool already knows the ports. I don’t think about it.
Spun up a new app on a server? Hit r to re-scan and it’ll show up. The deterministic port assignment means the new container immediately gets its own stable port — no configuration needed on the client side beyond adding the connection once.
The quit animation dissolves the screen character by character like the Snap. I spent way too long on it and I regret nothing.
Config #
It’s a YAML file at ~/.config/drillbit/config.yaml. DrillBit scaffolds one with comments on first run. Here’s roughly what mine looks like:
hosts:
- name: prod-1
env: prod
databases:
- container: myapp_db_1
auto: true
- container: analytics_db_1
auto: true
- name: prod-2
env: prod
# omit databases to discover everything on this host
- name: staging
env: staging
databases:
- container: myapp_db_1
password: the-staging-password
database: myapp_staging
The env label gets a deterministic color in the TUI (hashed, so prod is always the same color). Per-container overrides let you fix credentials when the Docker env vars don’t have the full picture. You can also edit overrides live in the TUI and they get saved back to the config.
Before and After #
Before DrillBit, I had a messy collection of shell aliases doing ssh -L for specific databases. Every new database meant editing a script. The ports were arbitrary and I kept forgetting which was which. Dead tunnels failed silently. I had to look up passwords every time. Adding a new host meant another terminal window with another ssh -L running in it.
Now I run drillbit, everything shows up, and I hit Space on the ones I want. My database tools already have the connection strings saved because the ports never change. That’s it. That’s the whole improvement, and it’s enough.
Install #
brew install jclement/tap/drillbit
Or grab a binary from GitHub releases. It’s a single static binary, no runtime dependencies.
The source is on GitHub if you want to poke around. It’s pure Go and it does one thing. I like it when tools do one thing.