Env Vault
Inject env into your deploy with a single bootstrap token; later changes don't need a rebuild. Use getEnv() on the server or useEnv() on the React side — public/private split is automatic.
Overview#
Env Vault provides runtime env management for self-hosted applications. Instead of using process.env, your app authenticates to Sentroy core with a single API key and pulls every registered env. When an admin changes a value, your app picks it up after the cache TTL expires — no Docker rebuild required.
Flow
1. Create a project in the Sentroy admin panel (e.g. my-blog).
2. Add environments (e.g. dev, prod) and variables under the project. Each variable carries a public flag.
3. Generate a token for the project + environment pair. The plaintext is shown only once.
4. Set the token as SENTROY_ENV_API_KEY in your deploy environment.
5. Use getEnv("KEY") or useEnv("KEY") in your app.
Bootstrap#
Single env: SENTROY_ENV_API_KEY. Everything else comes from the vault.
# Coolify (or any deploy environment)
SENTROY_ENV_API_KEY=stk_...
# Optional — defaults to https://sentroy.com
SENTROY_ENV_API_URL=https://sentroy.comThat's it. Every other env (DATABASE_URL, BETTER_AUTH_SECRET, NEXT_PUBLIC_TURNSTILE_SITE_KEY, etc.) lives in the Sentroy admin panel.
Install#
The vault lives under the /vault subpath of @sentroy-co/client-sdk — same package as the mail/storage SDK.
npm install @sentroy-co/client-sdkServer-side: getEnv()#
Async, in-memory cache (TTL 5 min). One HTTP fetch on first call — every subsequent call is in-process.
import { getEnv, getEnvOrThrow, preloadEnv } from "@sentroy-co/client-sdk/vault"
// Load early at process boot — fail-fast on missing envs
await preloadEnv()
// Async — returns undefined if the env is missing
const dbUrl = await getEnv("DATABASE_URL")
// Throws if missing — config-validation pattern
const turnstile = await getEnvOrThrow("BETTER_AUTH_TURNSTILE_SECRET")Cache
Default TTL is 5 minutes. For webhook- or admin-driven invalidation use refreshEnvCache(). To change the TTL at runtime use setEnvCacheTTL(seconds).
React: useEnv()#
Inject public envs from a server component during SSR; the client useEnv() hook reads them synchronously.
// app/layout.tsx (server component)
import { getPublicEnvs } from "@sentroy-co/client-sdk/vault"
import { EnvProvider } from "@sentroy-co/client-sdk/vault/react"
export default async function RootLayout({ children }) {
const envs = await getPublicEnvs()
return (
<html>
<body>
<EnvProvider envs={envs}>{children}</EnvProvider>
</body>
</html>
)
}// any client component
"use client"
import { useEnv } from "@sentroy-co/client-sdk/vault/react"
export function CaptchaWidget() {
const siteKey = useEnv("TURNSTILE_SITE_KEY")
if (!siteKey) return null
return <Turnstile siteKey={siteKey} />
}REST API#
Direct access via curl/fetch, without the SDK.
GET /api/env-vault/fetch
Returns every env in the token's scope (public + private). For server-side use.
curl -H "Authorization: Bearer stk_..." \
https://sentroy.com/api/env-vault/fetchGET /api/env-vault/public
Only variables flagged public: true. Browser-safe.
curl -H "Authorization: Bearer stk_..." \
https://sentroy.com/api/env-vault/publicEncryption#
Variable values are encrypted at rest with AES-256-GCM.
The master key lives on Sentroy core in the SENTROY_ENV_MASTER_KEY env. Plaintext is never written to the database — only ciphertext + nonce + auth tag (v1:iv:tag:cipher base64). Decryption happens in the token-auth fetch response.
# Generate a master key (one-time, store in Coolify env)
openssl rand -base64 32
# Add to platform: SENTROY_ENV_MASTER_KEY=<output>Audit log#
Every change records who/what/when — never the value itself.
The audit log never stores the value itself; it writes a sha256(plaintext)checksum as before/after. That makes "did the value change?" comparable, while a log compromise will not leak any plaintext.