This document describes how this project uses Infisical for secrets and override configuration alongside the existing Docker Compose workflow. Goal: .env holds orchestration and non-secret defaults; .env.local is exported from Infisical at startup and injected into the WordPress container. Infisical project, environment, and connection settings are provided entirely via environment variables.
Related files:
start: logs in withinfisical/clibefore startup and exports.env.localdocker-compose.yml:wordpress_frankenphploads both.envand.env.local.env.default: commit-safe local default env template.env.infisical.example: commit-safe Infisical connection template; copy to.env.infisicaland fill in real values
Self-hosted example: https://secret-manager.it-consultis.net.
Infisical environment variables
| Variable | Purpose |
|---|---|
INFISICAL_DOMAIN | Self-hosted or Cloud API URL |
INFISICAL_ORGANIZATION_ID | Organization ID used at login |
INFISICAL_PROJECT_ID | Infisical Project ID passed to --projectId for export / run |
INFISICAL_ENV | Infisical Environment slug, e.g. local, production-us |
INFISICAL_EMAIL / INFISICAL_PASSWORD | Local personal login (do not commit) |
INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET | Machine Identity (recommended for CI / servers) |
Variable layering
| Kind | Examples | Where to store |
|---|---|---|
| Compose orchestration | COMPOSE_PROJECT_NAME, WORDPRESS_HTTP_PORT, VERSION, CONTAINER_REGISTRY, DOCKER_NETWORK_* | .env (generated from .env.default) |
| Environment-specific config | ENVIRONMENT, WORDPRESS_SITE_URL, DB host | May stay in .env; migrate to Infisical over time |
| Secrets / sensitive overrides | WORDPRESS_*_KEY, WORDPRESS_*_SALT, JWT_AUTH_SECRET_KEY, DB password, admin password, etc. | Infisical → exported as .env.local |
| Infisical connection & auth | Domain, Organization ID, Project ID, INFISICAL_ENV, login credentials | Local .env.infisical (do not commit) |
Do not put Infisical personal email/password or Machine Identity client secrets in start or any committed file.
File conventions
.env # Local orchestration and defaults (gitignore)
.env.default # Commit-safe template; copied to .env on first start
.env.local # Infisical export output (gitignore)
.env.infisical # Infisical connection/auth (gitignore)
.env.infisical.example # Commit-safe template without real passwords
Ensure .gitignore includes at least:
.env
.env.local
.env.infisical
Startup flow
Order of operations in ./start:
- If
.envis missing, copy it from.env.default. - Load Infisical connection settings from
.env.infisical(or already-exported environment variables). - Log in with the
infisical/cliimage and obtain a short-livedINFISICAL_TOKEN. - Run
infisical export --projectId=... --env=...and write.env.local. source .env, create the network if needed, and run./docker-compose up.
Example WordPress service in docker-compose.yml:
services:
wordpress_frankenphp:
env_file:
- path: ./.env
required: true
- path: ./.env.local
required: false
- Later
env_fileentries override earlier ones with the same key (this applies to serviceenv_filemerge only). required: false: if export fails or Infisical is not configured locally, the stack can still start from.envalone (depending on whether the app needs those secrets).
Local auth file example
Copy the template and fill in values:
cp .env.infisical.example .env.infisical
Example .env.infisical contents:
INFISICAL_DOMAIN=https://secret-manager.it-consultis.net
INFISICAL_ORGANIZATION_ID=<organization-id>
INFISICAL_PROJECT_ID=<project-id>
INFISICAL_ENV=local
# Personal login on a developer machine only
INFISICAL_EMAIL=<your-email>
INFISICAL_PASSWORD=<your-password>
# Or use Machine Identity (recommended for CI / shared servers)
# INFISICAL_CLIENT_ID=<client-id>
# INFISICAL_CLIENT_SECRET=<client-secret>
Suggested pattern in start — load the file, then call the CLI:
if [ -f .env.infisical ]; then
# shellcheck disable=SC1091
source .env.infisical
fi
: "${INFISICAL_DOMAIN:?INFISICAL_DOMAIN is required}"
: "${INFISICAL_PROJECT_ID:?INFISICAL_PROJECT_ID is required}"
: "${INFISICAL_ORGANIZATION_ID:?INFISICAL_ORGANIZATION_ID is required}"
export INFISICAL_TOKEN=$(docker run --rm infisical/cli login \
--domain "${INFISICAL_DOMAIN}" \
--email "${INFISICAL_EMAIL}" \
--password "${INFISICAL_PASSWORD}" \
--organization-id "${INFISICAL_ORGANIZATION_ID}" \
--plain \
--silent)
docker run --rm infisical/cli export \
--domain "${INFISICAL_DOMAIN}" \
--env="${INFISICAL_ENV:-local}" \
--projectId="${INFISICAL_PROJECT_ID}" \
--token="${INFISICAL_TOKEN}" > .env.local
For multiple environments, select the Infisical environment with INFISICAL_ENV (optionally mapped from ENVIRONMENT), e.g. local, develop-us, production-eu.
Machine Identity (CI / servers)
export INFISICAL_TOKEN=$(docker run --rm infisical/cli login \
--domain "${INFISICAL_DOMAIN}" \
--method=universal-auth \
--client-id "${INFISICAL_CLIENT_ID}" \
--client-secret "${INFISICAL_CLIENT_SECRET}" \
--plain \
--silent)
Store credentials in the deployment platform or private server config — never in Git.
Compose interpolation vs override order (important)
Docker Compose has two separate environment-variable mechanisms:
- Project-level
.env(or--env-file): used for${VAR}interpolation in Compose files (image names, ports, network names, etc.). - Service
env_file: injected into the container process environment.
Also note: if a service defines environment:, those values override same-named keys from env_file. Interpolation such as ${WORDPRESS_DB_PASSWORD:-root} only reads the project-level .env and does not automatically read .env.local.
Therefore:
- Do not also declare secrets that live only in
.env.localunderenvironment:with${...}, or project.envdefaults will win. - Keep Compose orchestration variables in
.env. - Put secrets and runtime overrides in Infisical →
.env.local, and inject them viaenv_filerather than duplicating them inenvironment:.
Recommended container-side precedence:
.env (defaults) → .env.local (Infisical overrides) → environment: (non-secret, intentionally hard-coded items only)
Infisical project setup
- Create a Project in Infisical for this repo and set
INFISICAL_PROJECT_IDlocally. - Create Environments aligned with existing
ENVIRONMENTvalues when possible (e.g.local,uat-us,production-eu), and setINFISICAL_ENVaccordingly. - Import secrets and environment-specific overrides into each Environment.
- Local development: personal account or a dev Machine Identity with read access to
localonly. - CI / regional servers: separate Machine Identities with least privilege, read-only on the matching Environment.
Over time, sed-based edits in deploy/scripts/modify-dot-env-*.sh for secrets and regional differences can be replaced by per-environment infisical export, reducing long-lived hand-edited .env files on disk.
Day-to-day usage
# 1. Prepare .env (first time)
cp .env.default .env # or let ./start copy it
# 2. Prepare Infisical connection settings (first time)
cp .env.infisical.example .env.infisical
# Edit .env.infisical: Project ID, Organization ID, login credentials, etc.
# 3. Maintain secrets in the matching Infisical environment
# 4. Start (refreshes .env.local and brings containers up)
./start -d
Verify the container received Infisical variables:
./docker-compose exec wordpress_frankenphp env | grep -E 'INFISICAL_TEST|WP_REDIS_PREFIX|JWT_AUTH'
Security notes
- Do not commit Infisical passwords, client secrets, or a full
.env.localto Git, and do not bake them into images. - If credentials were ever written into
startor chat logs, rotate the password or revoke the client secret in Infisical immediately. INFISICAL_TOKENis short-lived and only for that export run; do not persist it in image layers.- Prefer Machine Identity in production so personal account passwords never appear in server scripts.
Optional later evolution
The current approach (host-side export → .env.local → env_file) is low-churn and fits a gradual migration from existing deploy scripts.
To reduce secrets written to disk on servers later:
- Install the Infisical CLI in the image;
- Wrap the container
CMDwithinfisical run --projectId=... --env=... -- ... /start; - Pass only
INFISICAL_TOKEN(andINFISICAL_DOMAINfor self-hosted) via Compose.
A thin orchestration .env is still required, because Compose resolves ${...} before the container fetches secrets at startup.