Originally published on Programming Securely. Cross-posted here from my development journal.
Keeping test accounts and SMTP credentials in project folders worked until every project needed its own copy. I wanted a central place where my development machines could retrieve those values, while keeping the existing encrypted files available for recovery. I set up Pinterest Knox on Unraid, backed by PostgreSQL, and added a small internal service that distributes the public certificate and client installers.
This is a deliberately shared LAN deployment: permitted devices receive administrator access without a Knox login. That makes setup convenient, but it means any device on the allowed subnet can read, change, or delete every stored secret. It is suitable only when that entire network is an acceptable trust boundary. A guest network, public reverse proxy, or internet-facing deployment needs a different authentication policy.
What runs on Unraid
The stack contains three containers. Knox serves its API over HTTPS, PostgreSQL stores encrypted values on persistent storage, and a separate bootstrap service serves public installation files over HTTP. PostgreSQL has no published port, and the bootstrap container cannot read Knox's secret directory.
Development device
├─ HTTP → Bootstrap service ← Public CA certificate
└─ HTTPS with pinned CA → Knox API → PostgreSQL
↑
Private runtime filesThe wrapper uses the Pinterest Knox library, pinned to commit 4f3284e590fe9105c0ca258f9bb92fd7fd284f87. It adds a persistent PostgreSQL backend, AES-256-GCM encryption for values, TLS, request limits, and network checks. This is a custom deployment wrapper; it is not the upstream development server or an official Pinterest Unraid image.
The deployment source and instructions include the Compose stack, Dockerfiles, bootstrap scripts, client, and tests.
Prepare storage and recovery material
I keep the deployment source in /mnt/user/appdata/knox/deploy, PostgreSQL data in /mnt/user/appdata/knox/postgres, runtime credentials in /mnt/user/appdata/knox/secrets, public certificate material in /mnt/user/appdata/knox/public, and database backups in /mnt/user/appdata/knox/backups.
The initial bootstrap script generates a certificate authority, a server certificate and key, a database password, and the database encryption key. Run it once into a new private directory, following the source README. It refuses to overwrite an existing directory.
Only the server's certificate, private key, public CA certificate, database password, and encryption key belong on Unraid. Keep the CA private key and a recovery copy of the other material somewhere separate and protected. Copy only the public CA certificate into the bootstrap service's public directory.
Runtime files are readable by the Knox container's numeric user, but not by arbitrary local users. The Knox and bootstrap containers run without root, with read-only filesystems and dropped capabilities. Those restrictions complement the application checks; they do not turn shared administrator access into individual authorization.
Build and start the stack
The repository is configured for my host at 192.168.1.176 and subnet 192.168.1.0/24. Adapt the bind address, peer allowlist, certificate subject alternative names, installer URLs, and pinned certificate hash together when deploying on another network. The bootstrap server intentionally refuses a certificate that differs from its configured fingerprint.
Once the source and private runtime files are in place:
cd /mnt/user/appdata/knox/deploy
docker compose build
docker compose up -d
docker compose ps
The Knox API listens on port 9443, and the public bootstrap service listens on 9080. Bind published ports to the LAN address. The application also checks the actual network peer against the allowed subnet, rather than accepting a client-supplied forwarded address. Knox rejects foreign browser origins, and neither service enables wildcard CORS.
I registered the stack in Unraid's Compose Manager with autostart and Docker WebUI links. Keep the registered Compose file aligned with the source, using the absolute deployment directory for build contexts. Docker restart policies are enabled; a full host reboot is a separate operational check.
Install a client on Windows, Ubuntu, or macOS
Open http://192.168.1.176:9080/ from a permitted LAN device. The page offers a Windows PowerShell installer, an Ubuntu Bash installer, and a macOS Bash installer. Each platform has x64 and ARM64 client builds, covering Intel and Apple Silicon Macs as well.
The installers download the public CA and the appropriate native Knox binary, verify both against hashes embedded in the installer, and configure the client under the current user's home directory. They preserve an existing config.json, refuse a conflicting CA, and leave the operating system and browser trust stores unchanged. No administrator privileges are required. Ubuntu needs Bash, curl, and OpenSSL; the macOS installer uses those existing command-line utilities too.
There is an important first step: verify the installer itself against the hash recorded in a trusted copy of the project's AGENTS.md. A checksum downloaded beside a script over HTTP does not establish trust, because the same interception could replace both. Download the script, check its hash independently, inspect it, then execute it. Do not pipe the HTTP response straight into a shell.
For example, on macOS:
curl --noproxy '*' --fail --output setup-macos.sh \
http://192.168.1.176:9080/setup-macos.sh
shasum -a 256 setup-macos.sh
# Compare with the independently obtained AGENTS.md hash, then inspect it.
bash setup-macos.sh
On Ubuntu, substitute setup-ubuntu.sh and use sha256sum. On Windows, download setup-windows.ps1, use Get-FileHash -Algorithm SHA256, compare the expected hash, and run the inspected script in PowerShell according to the machine's execution policy.
The client is installed in ~/.local/bin (knox.exe on Windows). Add that directory to your user PATH if necessary. Its configuration lives at ~/.config/knox/config.json; the public CA is beside it as ca.crt.
Once installed, this checks the HTTPS service without printing a credential:
curl --noproxy '*' --cacert ~/.config/knox/ca.crt \
https://192.168.1.176:9443/healthz
Knox is an API and command-line service. The HTTPS root page reports service information; it is not a browser-based vault dashboard. Installing client trust does not automatically make a browser trust the CA.
Keep agent instructions useful without copying credentials
Each project's AGENTS.md records the service address, verified installer hashes, setup instructions, and that project's actual secret ID. Shared mailbox and SMTP settings use separate IDs. The instructions also explain which tester accounts are still pending, so an available vault entry is never mistaken for a successful application signup.
An agent retrieves a value when needed rather than copying a password into documentation. knox get prints plaintext, so automation should capture that output in memory or use the included Python helper to verify retrieval without displaying the content. Importing a file creates a snapshot; it does not continuously synchronize future edits from the original folder.
Back up the database and the encryption key
A database backup alone is insufficient. Values need the original database encryption key to be decrypted. Keep the database dump and a protected recovery copy of that key, along with the database credentials and TLS recovery material.
The included scripts/backup.sh creates a PostgreSQL custom-format dump with private file permissions and checks that PostgreSQL can read its catalog. It does not install an automatic backup schedule. Restore into a separate empty database first, verify the result, and only then plan any replacement of a live database.
The server certificate also needs renewal before its one-year expiry. Renew TLS independently from the encryption key: replacing the encryption key without a data migration makes existing encrypted records unreadable. If the CA itself changes, rebuild the pinned installers and update the independently trusted fingerprints in the project instructions.
What I verified
The original vault migration verified byte-for-byte retrieval of the imported records, retrieval after a container restart, version promotion, and restoration of a database backup into a temporary database. The bootstrap service adds tests for the network boundary and exact public-file allowlist; private-key paths and traversal requests are denied.
The macOS Apple Silicon installer completed on the Mac, and the Ubuntu ARM64 installer completed in an Ubuntu 24.04 container; both connected to Knox over verified HTTPS. All six client binaries cross-compiled successfully. Native Windows, Intel Mac, and Ubuntu x64 execution remain unverified. The same distinction applies to availability: healthy containers and restart policies do not replace a host-reboot test or a practiced recovery procedure.