Managing secrets for a small deployment
A secret is anything that grants access: API keys, passwords, private keys, tokens.
Where secrets live
-
Never in the repository, public or private. Git history keeps everything forever; deleting the line later doesn't help.
-
In CI: GitHub Secrets (repository or per environment). Encrypted at rest, masked in logs, only readable by workflows.
-
On the server: a file outside git with tight permissions, loaded as environment variables.
umask 077 && echo 'API_KEY=...' >> secrets.env # file readable only by youWith Docker Compose:
env_file: [secrets.env]on the service, andsecrets.envin.gitignore. -
In the app: read from
process.envat startup; never log them, never send them to the browser.
Getting them from CI to the server
Make it generic, so a new key needs no code change. One pattern:
- Name every app secret with a prefix:
APP_TRANSLATOR_KEY,APP_SMTP_PASSWORD. - In the deploy job, expose all secrets as JSON (
toJSON(secrets)), select theAPP_ones withjq, strip the prefix. - Send the resulting
NAME=valuelines over SSH stdin, not as command-line arguments (those show up inpsand logs). - A deploy script on the server upserts each line into
secrets.env(replace the line with the same name, keep the rest) and restarts the app.
Things that leak secrets
-
Pasting command output: many CLIs print keys (
az ... keys list). Pipe straight into the file instead:echo "KEY=$(az ... keys list --query key2 -o tsv)" >> secrets.env -
echowith secrets on a shared screen, shell history (HISTCONTROL=ignorespaceand a leading space help), screenshots, chat messages. -
Error messages that dump the environment.
When one leaks
- Rotate: regenerate the key at the provider (services usually give two keys so you can switch with no downtime) and update it where it is used.
- Check usage and billing for abuse.
- If rotating isn't worth it, decide consciously: what can someone do with it, and what would it cost? A free-tier key at worst burns the free quota; a cloud admin key can cost thousands.
Related: CI/CD with GitHub Actions and GHCR, SSH keys.