Skip to content
sloop

Databases

Reaching a database over SSH

A database whose port is closed to the world, reached through an SSH server — by running the ssh already on your machine, never a client built into sloop.

Most databases worth backing up are not listening on the public internet. sloop reaches one through an SSH server you can already log into: it opens a forward, binds a local port, and hands that port to pg_dump or mysqldump, which never learn that SSH was involved.

Registering one

The SSH settings live on the record, beside the connection, so you give them once and every command after that just takes the name:

through a bastion
$ sloop db add remote --url postgres://app@127.0.0.1:5432/orders \$ --ssh-host bastion.example.com --ssh-user deployregistered remote in the project registry (the nearest .sloop at or above the working directory), password from the OS keyring postgres://app@127.0.0.1:5432/orders through ssh://deploy@bastion.example.com:22 over deploy@bastion.example.com, from the agent 127.0.0.1:5432 is as bastion.example.com sees it, not as this machine does

--host is what the SSH server sees, not what you see

This is the one thing that catches people, so sloop says it at registration time. --host 127.0.0.1 above means localhost as the bastion sees it — the database running on the bastion itself. A database on a third machine would be its address on the bastion's network. And --ssh-port is the SSH server's port, never the database's.

and it stays on the record
$ sloop db listremote postgres://app@127.0.0.1:5432/orders project · the OS keyring over deploy@bastion.example.com, from the agent

--ssh-user falls back to ~/.ssh/config and then to your own username. --ssh-port is 22 when left out. --ssh-identity takes a particular private key exactly as ssh -i does — and when you leave it out, the agent and ~/.ssh/config decide, which is the documented default. sloop never reads your key file and never copies it.

Where the key's passphrase comes from

The same four routes a database password takes, with the same flags wearing an --ssh- prefix: --ssh-keyring, --ssh-encrypted-file, --ssh-env <VARIABLE> and --ssh-passphrase-from <COMMAND>, plus --ssh-passphrase-stdin for storing one with no terminal.

a named key, unlocked from a vault
$ sloop db add keyed --url postgres://app@127.0.0.1:5432/orders \$ --ssh-host bastion.example.com --ssh-user deploy \$ --ssh-identity ~/.ssh/id_ed25519 \$ --ssh-passphrase-from "op read op://vault/ssh/passphrase"note: the password command exited with 1: op read op://vault/ssh/passphrase — registered anyway, because the command is read on every run rather than kept hereregistered keyed in the project registry, password from the OS keyring postgres://app@127.0.0.1:5432/orders through ssh://deploy@bastion.example.com:22#C:/Users/you/.ssh/id_ed25519 over deploy@bastion.example.com, unlocked from the command `op read op://vault/ssh/passphrase`

A registry holds the route and never the passphrase, which is exactly the rule database passwords follow. And the note is the point rather than a problem: a command that is not installed yet is fine at registration time, because it is run when it is needed rather than now.

and off again
$ sloop db edit remote --no-sshchanged remote in the project registry postgres://app@127.0.0.1:5432/orders through ssh://deploy@bastion.example.com:22 postgres://app@127.0.0.1:5432/orders

--no-ssh takes the forward off and the connection becomes a direct one.

One forward per session, not per command

sloop opens one long-lived forward per remote server — the first time something needs it — and closes it when sloop exits. Ten operations against one server in one menu session authenticate once. A tool that made you touch a hardware key before every single command would be a tool people stop using.

Why sloop runs your ssh rather than embedding one

This is a deliberate decision and it is the same one sloop makes about curl. Four reasons, and the first is the product:

  1. The guarantee survives literally

    An embedded SSH client is a socket in the binary. The promise above the fold would stop being true while a check that only looked for HTTP clients still passed — so the check is not narrow, and ssh2, libssh2-sys, russh, thrussh, async-ssh2-*, ssh-rs and makiko all fail the build alongside reqwest and hyper.

  2. Your own SSH already works

    ProxyJump through a bastion, IdentityFile, the agent, a hardware key, Match blocks, a corporate CA. Anybody whose ssh user@server works gets a working sloop with nothing else configured, because it is the same ssh.

  3. known_hosts stays OpenSSH’s

    sloop does not weaken host checking by a single option. The first connection to an unknown server is OpenSSH asking you, exactly as it would if you had typed the command yourself.

  4. Not one adapter changes

    sloop binds a local port that comes out at the database on the far side. psql, pg_dump, mysqldump and the rest connect to 127.0.0.1 and know nothing about SSH at all — the only thing that changed is which address they were handed.

Your credentials never leave your machine, and this is where that claim would have been quietly broken. An embedded SSH client can open a socket to anywhere. So the build refuses one — the same check that denies reqwest and hyper denies ssh2, russh, thrussh and the rest, across normal, build and dev dependencies, on every target.

checked on every push
$ sh ci/no-http-client.shno-http-client: 269 crates in the graph, not one of them opens a socket.

A claim that holds only because the check is narrow is not worth making. That is why the check is not narrow — and why the honest way to reach a closed-off database is to run the ssh you already trust.

When it fails, it fails in OpenSSH's words

Which is the best evidence there is for everything above. Here the bastion does not exist:

exit 3
$ sloop db test remote remote postgres://app@127.0.0.1:5432/orders through ssh://deploy@bastion.example.com:22 project✗ ssh could not reach deploy@bastion.example.com hint: ssh: Could not resolve hostname bastion.example.com: Name or service not known

ssh: Could not resolve hostname … is not sloop's sentence. It is the message your own OpenSSH printed, handed straight back to you — so a wrong key, a rejected host, an expired certificate or a ProxyJump that failed all arrive as the message you would have seen typing ssh yourself, and you fix them the way you already know how.

Every --ssh- flag is on the command reference, generated from --help.

sloop — a database operations CLI for PostgreSQL, MySQL and MariaDB. MIT or Apache-2.0.