QoreDB LogoQoreDB
Back to blog
SecurityTechnical journal

How to Encrypt Your Database Credentials Locally

A backend developer quickly accumulates a dozen database credentials: a local PostgreSQL, an integration MySQL, a staging Mongo, a Redis behind a bastion, sometimes a remote SQL Server over SSH. The temptation is to dump everything flat into a config file, a .env, or directly…

Raphaël – Creator of QoreDBRaphaël – Creator of QoreDB
6 min readUpdated Jul 18, 2026
How to Encrypt Your Database Credentials Locally

A backend developer quickly accumulates a dozen database credentials: a local PostgreSQL, an integration MySQL, a staging Mongo, a Redis behind a bastion, sometimes a remote SQL Server over SSH. The temptation is to dump everything flat into a config file, a .env, or directly into the shell command that ends up in the history. It's fast, it's readable, and it's exactly what malware looks for first on a compromised machine.

The subject of this article is concrete: how to store these credentials locally without leaving them lying around in plaintext, and which mechanisms are reliable. We'll review the classic config files, the OS keychain, command-line password managers, and the vaults built into database clients.

The trap of plaintext config files

The .pgpass in PostgreSQL, the .my.cnf in MySQL, or the .env files in node or Python projects all pose the same problem: passwords are in plaintext on disk. The only protection is the file permission (0600 on Unix). That's enough against another user of the system, but not against a malicious process running under your own account.

A second, more subtle risk is leakage through backups: dotfiles synced to a Git repo, Time Machine snapshots, indexing by a desktop search tool. A plaintext password in a file touched by one of these routines can potentially end up somewhere other than the local machine. The basic rule is therefore simple: no plaintext password in a persisted file, unless the file itself lives in a dedicated encrypted volume that is excluded from any synchronization.

Environment variables are a marginal improvement: they aren't written to disk by the shell itself, but they show up in /proc/<pid>/environ on Linux, in process inspection on macOS, and in the context of any child process. For occasional use in CI it's acceptable; for the machine of a developer who opens ten shells a day, it's not enough.

The OS keychain as a foundation

Every operating system exposes a native secret-storage service: Keychain on macOS, gnome-keyring or kwallet via libsecret on Linux, Credential Manager on Windows. The principle is the same: secrets are encrypted at rest with a key derived from the user's password, and access goes through an API that verifies the identity of the calling process. On macOS and Windows, the system can also require interactive confirmation (Touch ID, Hello, or re-entering the password).

The practical benefit is threefold: the secret is never in plaintext on disk, the OS handles encryption and automatic unlocking at session login, and another user of the machine can't read it. The main drawback is the lack of portability: a keychain stays tied to an OS session, and you don't sync it between machines as-is.

In practice, you access the keychain in several ways. On the command line, security add-generic-password on macOS, secret-tool store on Linux, cmdkey /add on Windows. From code, most languages expose a binding: keyring in Python, keytar in Node, the keyring crate in Rust. All of them abstract the three backends behind a common API.

Command-line password managers

When you want to share credentials across several machines or with a team, the OS keychain is no longer enough. Password managers like 1Password, Bitwarden, or pass expose a CLI that lets you fetch a secret on demand inside a script or a shell alias. The secret stays encrypted in the central vault and is only decrypted in the context of the call.

The typical pattern is an alias or a shell function that retrieves the password just before running the command, without ever exporting it into the persistent environment. With the 1Password CLI, for example, that gives a call to op read 'op://Personal/postgres-prod/password' injected into the command at execution time. The secret is never written to a file.

This model has two merits: it centralizes credentials in an auditable place, and it forces an explicit unlock via Touch ID, biometrics, or a master password. The trade-off is a dependency on an external service to access your local databases, which isn't always desirable for a dev database that must remain accessible offline.

The vault of a database client

Modern desktop clients handle credential storage directly. Two approaches coexist. The first, used by DBeaver, encrypts the saved connections with a master password. Everything is stored in the application's files, but the content is only readable after unlocking. The second, adopted by QoreDB and other recent clients, delegates secret storage to the OS keychain and keeps only the connection metadata on disk (host, port, username, TLS options).

The choice between the two depends on the context. The master password has the advantage of being portable: you can copy the config files to another machine and the vault follows. The OS keychain, on the other hand, doesn't force the user to remember an additional password, benefits from the system's biometric integration, and never exposes secrets in a file that a backup tool could suck up. For single-user desktop use, it's the simplest and safest route.

In QoreDB, it's this second approach that is implemented. When you save a connection, the metadata goes into a JSON file in the app's configuration folder, and the password goes into the keychain via the keyring crate. The SSH password, a private key's passphrase, and a proxy password all follow the same path. An optional vault lock, based on Argon2id, adds an extra layer that forces an explicit unlock before any credential read, independently of the OS session.

A concrete workflow

For most developers working on a personal machine, the combination that works well is the following. Local databases and cloud dev databases go through the desktop client, which saves credentials in the keychain. Production databases accessed from the CLI go through a password manager with a shell alias that fetches the secret on the fly. CI credentials stay in the orchestrator's secrets system, never in the repo.

A few hygiene rules round out the approach. Rotate passwords regularly, especially after a team member leaves. Use separate accounts per environment, so that a leak of dev credentials doesn't grant access to prod. Enable 2FA on the databases that support it, and prefer certificate- or IAM-based auth methods when possible. And regularly audit the files in your home directory that might contain forgotten secrets: a grep -r 'password=' ~/ often reveals things you didn't suspect.

Conclusion

Encrypting your database credentials locally doesn't require complex tooling. The OS keychain solves the majority of cases for a solo developer. A password manager adds the team dimension and controlled sharing. A database client that builds on these primitives, rather than reinventing its own storage, naturally inherits their guarantees. The mistake to avoid remains the same as it was twenty years ago: leaving a password in plaintext in a config file and hoping that Unix permissions will do the job.

Newsletter

Stay updated on new releases

Subscribe to get product releases, new drivers notifications, and technical tutorials.

🎁 Bonus: Get our free SQL Performance Cheat Sheet — 9 pages, PostgreSQL / MySQL / SQLite (PDF)!
Share
How to Encrypt Your Database Credentials Locally - Blog - QoreDB