Skip to main content

Environments & Secrets

Environments

The platform runs in four environments: development, staging, production, and test. The active environment is set via the pixwel_environment env var.

How secrets are managed

Secrets are stored in AWS SSM Parameter Store and fetched at runtime. Nothing sensitive is committed to git or baked into Docker images.

SSM layout

Individual secrets (mongo URI, OAuth secrets, etc.) are stored at /platform/{environment}/{key} and injected into Lambda functions via api/serverless.yml.

How it works per deployment mode

Local dev (Codespace or laptop with docker-compose)

Fetches:
  • /platform/development/envdocker/development.env
  • /platform/development/cloudfront_pemdocker/pems/cloudfront.pem
  • /platform/aspera_client_pemdocker/pems/aspera.pem
install.sh calls this then copies docker/development.env to .env for docker-compose. docker-compose.yml mounts docker/pems/ into every container at /opt/pixwel/pems. The PHP bootstrap uses those files directly — no SSM calls at container startup, so offline development works after the initial fetch-secrets.sh run. Requires: AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY with SSM read access (one-time, for fetch-secrets.sh).

GitHub Actions (CI tests)

test.yml runs ./install/fetch-secrets.sh development before building the API test container.

GitHub Actions (deploy)

deploy.yml runs ./install/fetch-secrets.sh staging or ./install/fetch-secrets.sh production in the deploy jobs. The env file is copied to .env and passed to the ECS task via --env-file.

ECS containers (staging/production)

The container starts with the env file injected at runtime (--env-file). The PHP bootstrap (api/config/bootstrap/environments.php) fetches the signing keys from SSM once per container and caches them to a stable path ($TMPDIR/pixwel-pems); every request after the first reads the cached file. No keys are baked into the image, and there are no SSM calls in the request/response cycle — only the first request (or worker process boot) per container touches SSM.

Lambda (serverless API functions)

Secrets are injected at deploy time from SSM by serverless.yml via ${ssm:/platform/{env}/{key}} references. The PHP bootstrap caches cloudfront_pem / aspera_client_pem the same way as ECS — no runtime SSM calls.

AWS identities & access

Secrets access is controlled by IAM. There are a few distinct identities, by layer: SSM read grants live on the pixwel-platform-engineering group policy (for engineers + CI’s test user) and on the per-environment app users (platform-staging, platform-production). When adding a new SSM parameter, make sure the relevant identity is granted ssm:GetParameter on its path — e.g. the group policy currently allows /platform/development/* plus the shared /platform/aspera_client_pem.
Hardening backlog: engineering-test-user is a shared user with a long-lived static key, and CI stores static keys in secrets.AWS_ACCESS_KEY_ID. The modern pattern is GitHub Actions OIDC → sts:AssumeRole (no stored keys) and per-person credentials. Tracked separately.

Updating secrets

To update an env file:
To update a signing key, update the relevant SSM param directly. The change takes effect on the next container or Lambda cold start.

Adding a new secret

  1. Add to the SSM env file for each environment (/platform/{env}/env)
  2. For Lambda, also add an ${ssm:...} reference in api/serverless.yml
  3. No Dockerfile or git changes needed