Skip to content

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 0600 files on the host server.

1. Declaring Variables in epd.yml ​

Separate non-sensitive public configuration from private credentials:

yaml
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_SECRET

2. How Secret Resolution Works ​

When you run epd deploy:

  1. epd reads each secret named in env.secret from:
    • Your local shell environment (export DATABASE_URL=...)
    • .env or .env.local in your project root
    • Destination-specific files like .env.staging (when using -d staging)
  2. epd uploads these key-value pairs to the remote server as 0600 files under /var/lib/epd/apps/<app>/.
  3. During startup:
    • Docker Mode: Written to a 0600 file and passed to docker run via --env-file. The file is removed right after the containers start. Docker keeps the values in the container config.
    • Process Mode: Written to a 0600 file and handed to PM2 when the processes start. The file is removed right after pm2 start. PM2 keeps the environment it was started with, and saves it to its dump.

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>/)ModeWrittenRemoved
env.<service>dockerevery service start (deploy, rollback)right after docker run, also when it fails
env.acc.<name>docker, accessoriesevery accessory startright after docker run, also when it fails
exec-env.<random>docker, processevery epd exec (docker: not with --accessory or --reuse) and epd shell in process modeafter the command, also when it fails
envprocessevery build that has an install or build stepafter the build, also when it fails
pm2.<service>.<slot>.jsonprocessevery startright 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 run docker inspect on the server can read the values of every container, including the proxy.
  • The pm2 dump (~/.pm2/dump.pm2 of the SSH user): pm2 save records 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:

yaml
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_PASSWORD

4. Multi-Environment Overlays (Staging vs. Production) ​

Manage multiple environments without duplicating configuration files:

bash
# Deploys using epd.yml + merges epd.staging.yml + loads .env.staging
epd deploy -d staging

In epd.staging.yml:

yaml
# Only specify fields you want to override for staging:
servers:
  web:
    hosts: [staging-server-ip]
    domains: [staging.example.com]

env:
  clear:
    NODE_ENV: staging

Next Steps ​

Released under the MIT License.