> ## Documentation Index
> Fetch the complete documentation index at: https://help.pixwel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Environments

# 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

| Path                                   | What                                        |
| -------------------------------------- | ------------------------------------------- |
| `/platform/development/env`            | Full env file for local dev                 |
| `/platform/staging/env`                | Full env file for staging ECS containers    |
| `/platform/production/env`             | Full env file for production ECS containers |
| `/platform/development/cloudfront_pem` | CloudFront signing key (development)        |
| `/platform/staging/cloudfront_pem`     | CloudFront signing key (staging)            |
| `/platform/production/cloudfront_pem`  | CloudFront signing key (production)         |
| `/platform/aspera_client_pem`          | Aspera 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/env` → `docker/development.env`
* `/platform/development/cloudfront_pem` → `docker/pems/cloudfront.pem`
* `/platform/aspera_client_pem` → `docker/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:

| Where                                            | Identity                                                                                                          | Used for                                                          |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| CI — `fetch-secrets.sh` step                     | `engineering-test-user` (member of the `pixwel-platform-engineering` group) via `secrets.AWS_ACCESS_KEY_ID`       | reading SSM on the runner to write the env file + PEMs            |
| CI — serverless deploys                          | a dedicated serverless-deploy user (hardcoded key id in `deploy.yml`) + `secrets.ENG_AWS_SECRET_ACCESS_KEY`       | `serverless 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 / local                               | pass-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:

```sh theme={null}
# 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
