Secrets & environment

How HostSSH delivers environment variables and secrets to your apps — sealed at rest, decrypted only inside the agent, editable after deploy.

Secrets & environment

Every App gets configuration through environment variables. HostSSH splits them into two kinds and treats secrets very differently from plain values.

  • Plain env — non-sensitive values: NODE_ENV=production, feature flags, public URLs.
  • Sealed secrets — sensitive values: DATABASE_URL, API keys, tokens, signing keys.

The rule: secret values never travel or rest in plaintext. They're sealed by the control plane and decrypted only inside the Agent, at deploy time.


1. Setting env when you deploy

Dashboard: in the Deploy drawer, add variables in the Environment section. Mark the sensitive ones as secret — those get the sealed treatment.

CLI:

$ hostssh deploy --name shop --source ./shop \
    --env NODE_ENV=production \
    --env-file ./shop.env
  • --env K=V sets one variable (repeatable).
  • --env-file f reads KEY=VALUE lines from a file; individual --env flags override it.

For the CLI, treat any file you pass with --env-file as sensitive and keep it out of version control.


2. Env lives on the App and survives redeploys

Environment is attached to the App — the stable deployment row in the control plane — not to a particular container. So:

  • A redeploy keeps your env. You don't re-enter it every time.
  • Editing env is a first-class action. Go to App Settings → Environment Variables, change values, and click Save. The change is stored immediately; click Redeploy to apply to roll it into the running container.

This is why the platform can guarantee env doesn't silently vanish on a clean redeploy (guard G3).


3. Bulk editing: paste a whole .env

You never have to enter variables one at a time:

  • Paste to expand. Paste a multi-line .env blob into any KEY field and it expands into rows. Comments, blank lines, export prefixes, and quoted values are all understood; existing keys update in place and new keys append. A pasted KEY= with no value leaves a stored secret untouched.
  • Raw .env mode. Click Raw .env to edit the whole set as one text file — ideal for bulk reorder, delete, or paste. Secret and auto values appear as KEY= (their stored values are kept; they are never sent to the browser). Deleting a line deletes the variable.
  • Copy .env. One click copies everything in .env format for local dev or migration. Values the server holds sealed are exported as KEY= — secret material never leaves the platform this way.
  • Auto-minted secrets. Toggle a row to auto and the Agent mints a strong random value directly on the Node at deploy time — perfect for SESSION_SECRET-style values nobody needs to know. The value is never entered, stored in the control plane, or dispatched; it stays stable across redeploys.

4. How sealed secrets work (the security model)

When you mark a value secret, here's its journey:

  1. Sealed at the control plane. The value is encrypted (AES-GCM) into an envelope before it's ever written to the database. The database stores the envelope; the plaintext is never persisted, and the value is masked in every UI.
  2. Queued sealed. When a deploy Job is created, plain env goes in a public env field and secrets go in a separate secretEnv field as sealed envelopes. The job queue and its logs never contain plaintext secrets. (The system actively rejects a job that tries to carry a plaintext secretEnv.)
  3. Decrypted only inside the Agent. The Agent holds the decryption key (HOSTSSH_ENC_KEY) in its own environment. It opens the envelopes at deploy time, merges the secret values into the container's runtime environment, and persists them to a local 0600 secret store so redeploys and restarts don't need the control plane to re-send them.
  4. Never in logs. Deploy logs, job status, and dispatch payloads carry only masked references for secret values.

The consequence: someone with read access to the control-plane database, the job queue, or the deploy logs still cannot read your secrets. Only the Agent on the box — which is already the thing running your code — can.

The key that unlocks it: HOSTSSH_ENC_KEY is what seals and opens envelopes. Keep it consistent across your control plane and Agents, and back it up. If you ever move the control plane to a new machine, carry this key byte-for-byte — otherwise previously sealed secrets can't be decrypted. (See docs/dev/security-model.md.)


5. Where secrets should point (the private Mesh)

Your DATABASE_URL and other internal endpoints should use private Mesh addresses, not public IPs. HostSSH links your Nodes over a private WireGuard network, so an App on one Node reaches Postgres on another over 10.x addresses that are never exposed to the internet. For example:

DATABASE_URL=postgres://app:••••@10.10.0.2:5432/shop

This keeps your database off the public internet entirely. See Concepts and Databases (in Backups & recovery).


6. Storing provider credentials (BYOK connections)

Secrets that aren't app env — your R2/S3 keys, Cloudflare tokens, Hostinger API token — belong in the Connections vault, not in an App's env. They're sealed the same way and reused across features (backups to your bucket, DNS cutovers, provisioning). See Connections (BYOK).


7. Good habits

  • Mark anything sensitive as secret. When in doubt, seal it. There's no downside.
  • Use the Mesh for internal endpoints. Don't put a public database IP in an env var.
  • Rotate by editing + redeploying. Change the value in App Settings, redeploy, then revoke the old credential at its source.
  • Keep HOSTSSH_ENC_KEY and your license key backed up off the box (HostSSH can age-encrypt an off-box copy of platform secrets for you — see Backups & recovery).
  • Never commit env files. Add .env, *.env, and any --env-file targets to .gitignore.

Next steps