Env-var encryption
The design
Section titled “The design”Every environment variable a service uses is stored in the services.env_json column. Before writing, Pier encrypts the JSON with AES-256-GCM using a 32-byte key from /opt/pier/.env:
PIER_SECRET=<32 bytes base64>Encrypted values carry an ENC: prefix so Pier can distinguish them from legacy plain-text rows.
Why the key lives outside the database
Section titled “Why the key lives outside the database”If an attacker exfiltrates pier.db alone, they get nothing — the ciphertext is useless without the key.
If an attacker exfiltrates only .env, there is no data to decrypt.
An attacker needs both to read secrets.
This is the same model Coolify uses (Laravel APP_KEY + encrypted cast). Simpler than a Vault, and appropriate for a single-tenant self-hosted tool.
First-boot bootstrap
Section titled “First-boot bootstrap”On first run, if /opt/pier/.env does not exist, Pier generates a fresh PIER_SECRET and writes it with chmod 600. A one-time snapshot of the database is saved as pier.db.pre-encryption in case you need to roll back to plain-text mode for debugging.
Rotation
Section titled “Rotation”Rotation is rare. When needed:
- Stop Pier.
- Run the rotation helper (planned):
pier rotate-secret. - Start Pier.
Until the CLI helper ships, export env vars through the UI, rotate PIER_SECRET manually, and re-import.
What is not encrypted
Section titled “What is not encrypted”- Service names, images, ports, and domains — these are not secrets.
- Deployment metadata and logs — surface data, not credentials.
- The Traefik dynamic config files — they contain hostnames, not secrets.