Secrets & Environment Management
Learn how epd securely manages environment variables, protects production secrets from git, and supports multi-environment staging overlays.
The Core Principle
epd ensures secrets are:
- Never committed to git.
- Never baked into Docker image layers.
- Never printed in shell histories or CI logs.
- Saved as restricted root-owned
0600files on the host server.
1. Declaring Variables in epd.yml
Separate non-sensitive public configuration from private credentials:
env:
# Public, non-sensitive variables (safe to commit to git)
clear:
NODE_ENV: production
PORT: "3000"
LOG_LEVEL: info
# Sensitive secrets (read from your local environment or .env at deploy time)
secret:
- DATABASE_URL
- STRIPE_SECRET_KEY
- JWT_SECRET2. How Secret Resolution Works
When you run epd deploy:
epdreads each secret named inenv.secretfrom:- Your local shell environment (
export DATABASE_URL=...) .envor.env.localin your project root- Destination-specific files like
.env.staging(when using-d staging)
- Your local shell environment (
epduploads these key-value pairs to the remote server as0600files under/var/lib/epd/apps/<app>/.- During startup:
- Docker Mode: Written to a
0600file and passed todocker runvia--env-file. The file is removed right after the containers start. Docker keeps the values in the container config. - Process Mode: Written to a
0600file and handed to PM2 when the processes start. The file is removed right afterpm2 start. PM2 keeps the environment it was started with, and saves it to its dump.
- Docker Mode: Written to a
Missing Secrets
If a variable listed in env.secret is not found in your environment or .env file, epd halts deployment with a helpful error before modifying any remote state.
Where secrets live on the server
epd writes secret values to files under /var/lib/epd/apps/<app>/. Every such file is 0600, and each one is removed as soon as the step that reads it is done, including when that step fails. Nothing is kept for later use:
File (under /var/lib/epd/apps/<app>/) | Mode | Written | Removed |
|---|---|---|---|
env.<service> | docker | every service start (deploy, rollback) | right after docker run, also when it fails |
env.acc.<name> | docker, accessories | every accessory start | right after docker run, also when it fails |
exec-env.<random> | docker, process | every epd exec (docker: not with --accessory or --reuse) and epd shell in process mode | after the command, also when it fails |
env | process | every build that has an install or build step | after the build, also when it fails |
pm2.<service>.<slot>.json | process | every start | right after pm2 start, also when it fails |
The proxy keeps its DNS-challenge tokens in the same way, under /var/lib/epd/proxy/: env for docker mode and pm2.json for process mode, when proxy.dns_env is set. Each one is written right before the proxy starts and removed right after.
Older epd versions kept some of these files on the server. The next deploy deletes them: every env.* file (service and accessory files) under docker, and pm2.*.json and env under process mode.
After a deploy, two copies of the secret values remain on the server, and both need protecting:
- The container config (
docker inspect): anyone who can rundocker inspecton the server can read the values of every container, including the proxy. - The pm2 dump (
~/.pm2/dump.pm2of the SSH user):pm2 saverecords each process's environment there, so the values of every process started by pm2, including the proxy in process mode.
Restrict access to the server, not only to the files above.
3. Service-Specific Environments
You can also override environment variables for a specific server service:
servers:
web:
hosts: [203.0.113.10]
port: 3000
env:
clear:
SERVICE_NAME: web-frontend
worker:
hosts: [203.0.113.10]
command: bun run worker.ts
env:
clear:
CONCURRENCY: "10"
secret:
- REDIS_PASSWORD4. Multi-Environment Overlays (Staging vs. Production)
Manage multiple environments without duplicating configuration files:
# Deploys using epd.yml + merges epd.staging.yml + loads .env.staging
epd deploy -d stagingIn epd.staging.yml:
# Only specify fields you want to override for staging:
servers:
web:
hosts: [staging-server-ip]
domains: [staging.example.com]
env:
clear:
NODE_ENV: stagingNext Steps
- Set up Persistent Accessories & Databases
- Automate in CI/CD with GitHub Actions
- Review the CLI Commands Reference