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

PathWhat
/platform/development/envFull env file for local dev
/platform/staging/envFull env file for staging ECS containers
/platform/production/envFull env file for production ECS containers
/platform/development/cloudfront_pemCloudFront signing key (development)
/platform/staging/cloudfront_pemCloudFront signing key (staging)
/platform/production/cloudfront_pemCloudFront signing key (production)
/platform/aspera_client_pemAspera JWT signing key (shared across envs)
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)

./install/fetch-secrets.sh development
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:
WhereIdentityUsed for
CI — fetch-secrets.sh stepengineering-test-user (member of the pixwel-platform-engineering group) via secrets.AWS_ACCESS_KEY_IDreading SSM on the runner to write the env file + PEMs
CI — serverless deploysa dedicated serverless-deploy user (hardcoded key id in deploy.yml) + secrets.ENG_AWS_SECRET_ACCESS_KEYserverless deploy
App inside containers (ECS + CI test containers)platform-staging / platform-production (the aws_key/aws_secret in the env file)the app’s own AWS calls + the bootstrap’s per-container SSM fetch
Codespaces / localpass-through AWS_ACCESS_KEY_ID from Codespace/host env (typically a pixwel-platform-engineering group member)fetch-secrets.sh
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:
# Fetch current, edit locally, push back
./install/fetch-secrets.sh staging
# edit docker/staging.env
AWS_PROFILE=pixwel aws ssm put-parameter --region us-west-2 \
  --name /platform/staging/env --type SecureString \
  --value "$(cat docker/staging.env)" --overwrite
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