Infisical and Docker Environment Variables

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 with infisical/cli before startup and exports .env.local
  • docker-compose.yml: wordpress_frankenphp loads both .env and .env.local
  • .env.default: commit-safe local default env template
  • .env.infisical.example: commit-safe Infisical connection template; copy to .env.infisical and fill in real values

Self-hosted example: https://secret-manager.it-consultis.net.

Infisical environment variables

VariablePurpose
INFISICAL_DOMAINSelf-hosted or Cloud API URL
INFISICAL_ORGANIZATION_IDOrganization ID used at login
INFISICAL_PROJECT_IDInfisical Project ID passed to --projectId for export / run
INFISICAL_ENVInfisical Environment slug, e.g. local, production-us
INFISICAL_EMAIL / INFISICAL_PASSWORDLocal personal login (do not commit)
INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRETMachine Identity (recommended for CI / servers)

Variable layering

KindExamplesWhere to store
Compose orchestrationCOMPOSE_PROJECT_NAME, WORDPRESS_HTTP_PORT, VERSION, CONTAINER_REGISTRY, DOCKER_NETWORK_*.env (generated from .env.default)
Environment-specific configENVIRONMENT, WORDPRESS_SITE_URL, DB hostMay stay in .env; migrate to Infisical over time
Secrets / sensitive overridesWORDPRESS_*_KEY, WORDPRESS_*_SALT, JWT_AUTH_SECRET_KEY, DB password, admin password, etc.Infisical → exported as .env.local
Infisical connection & authDomain, Organization ID, Project ID, INFISICAL_ENV, login credentialsLocal .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:

  1. If .env is missing, copy it from .env.default.
  2. Load Infisical connection settings from .env.infisical (or already-exported environment variables).
  3. Log in with the infisical/cli image and obtain a short-lived INFISICAL_TOKEN.
  4. Run infisical export --projectId=... --env=... and write .env.local.
  5. 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_file entries override earlier ones with the same key (this applies to service env_file merge only).
  • required: false: if export fails or Infisical is not configured locally, the stack can still start from .env alone (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:

  1. Project-level .env (or --env-file): used for ${VAR} interpolation in Compose files (image names, ports, network names, etc.).
  2. 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.local under environment: with ${...}, or project .env defaults will win.
  • Keep Compose orchestration variables in .env.
  • Put secrets and runtime overrides in Infisical → .env.local, and inject them via env_file rather than duplicating them in environment:.

Recommended container-side precedence:

.env (defaults) → .env.local (Infisical overrides) → environment: (non-secret, intentionally hard-coded items only)

Infisical project setup

  1. Create a Project in Infisical for this repo and set INFISICAL_PROJECT_ID locally.
  2. Create Environments aligned with existing ENVIRONMENT values when possible (e.g. local, uat-us, production-eu), and set INFISICAL_ENV accordingly.
  3. Import secrets and environment-specific overrides into each Environment.
  4. Local development: personal account or a dev Machine Identity with read access to local only.
  5. 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.local to Git, and do not bake them into images.
  • If credentials were ever written into start or chat logs, rotate the password or revoke the client secret in Infisical immediately.
  • INFISICAL_TOKEN is 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.localenv_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 CMD with infisical run --projectId=... --env=... -- ... /start;
  • Pass only INFISICAL_TOKEN (and INFISICAL_DOMAIN for self-hosted) via Compose.

A thin orchestration .env is still required, because Compose resolves ${...} before the container fetches secrets at startup.

Related docs

Leave a Reply

Your email address will not be published. Required fields are marked *