# Every key and credential

One page with every value a Full app reads: what it is, whether you need it, how you get it, and a complete `.env` at the end. Nothing here is invented: each name is read by your app's `internal/app` code or its `compose.yaml`, and `.env.example` lists them all.

## How each value is obtained

| Label | Meaning |
|---|---|
| **Generated by you** | You create it on your computer with a command |
| **Created by aps** | `aps dev` fills it in `.env` for development |
| **Copied from a dashboard** | A website (Google, Apple, Resend…) shows it and you copy it |
| **Downloaded file** | A website gives you a file to keep |
| **Chosen by you** | Your own domain, address or name; nobody issues it |
| **Built into the app** | Created by the app while it runs; you never set it |
| **Default is fine** | Has a working default; change only if needed |

## Values you never create

People often look for these. **No manually generated key is required** for any of them.

| Value | Why you don't need one |
|---|---|
| JWT secret | The app doesn't use JWTs for sessions. A session is a random token; the database stores only its SHA-256 hash, so there's nothing to sign |
| Session secret, cookie secret | Session cookies hold that random token, not signed data |
| CSRF secret | Cross-site requests are blocked by checking the browser's `Origin` and `Sec-Fetch-Site` headers, not with tokens |
| Passkey server key | Each passkey's key pair lives on the person's device; your app stores only public keys. See [Passkeys](/sign-in/passkeys/) |
| Apple client secret | The app signs a fresh one for 5 minutes each time it talks to Apple, from your `.p8` key. See [Apple sign-in](/sign-in/apple/) |
| Webhook secret | The app receives one kind of webhook, Apple's notifications, and checks them with Apple's public keys |
| OAuth state or nonce keys | Random single-use values stored in the database for a few minutes |
| TOTP secrets | Created for each person when they turn on an authenticator app, encrypted with `AUTH_ENCRYPTION_KEYS` |
| Database password on your computer | `compose.yaml` sets it to your app's name; it only listens on `127.0.0.1` |

## Required to start

| Variable | Development | Production | Secret? | Obtained |
|---|---|---|---|---|
| `DATABASE_URL` | Yes: already in `.env` | Yes | **Yes** | Development: **created by aps** (copied from `.env.example`). Production: **copied from a dashboard** (your database provider) |
| `AUTH_ENCRYPTION_KEYS` | Yes for 2FA and seed data | Yes | **Yes** | Development: **created by aps**. Production: **generated by you**, see [Encryption key](/sign-in/encryption-key/) |
| `RESEND_API_KEY` (or the `SMTP_` values) | No: Mailpit is used | Yes | **Yes** | **Copied from a dashboard**, see [Email sending](/sign-in/email/) |

Generate a production encryption key:

```bash
echo "k1:$(openssl rand -base64 32)"
```

## Sign-in methods

| Variable | Needed for | Secret? | Obtained | Guide |
|---|---|---|---|---|
| `WEBAUTHN_RP_ID` | Passkeys in production | No | **Chosen by you**: your domain | [Passkeys](/sign-in/passkeys/) |
| `WEBAUTHN_ORIGINS` | Passkeys in production | No | **Chosen by you**: your https frontends | [Passkeys](/sign-in/passkeys/) |
| `WEBAUTHN_APPLE_APP_IDS` | Passkeys in iOS apps | No | **Copied from a dashboard**: Team ID + bundle ID | [Mobile passkeys](/sign-in/passkeys-mobile/) |
| `WEBAUTHN_ANDROID_APPS` | Passkeys in Android apps | No | **Copied from a dashboard**: package name + SHA-256 fingerprints | [Mobile passkeys](/sign-in/passkeys-mobile/) |
| `APP_PUBLIC_URL` | Google or Apple on the web, in production | No | **Chosen by you**: your API's https address | [Google](/sign-in/google/) |
| `GOOGLE_CLIENT_ID` | Google | No | **Copied from a dashboard**: Google Cloud Console | [Google](/sign-in/google/) |
| `GOOGLE_CLIENT_SECRET` | Google | **Yes** | **Copied from a dashboard**, shown when the client is created | [Google](/sign-in/google/) |
| `GOOGLE_IOS_CLIENT_ID` | Google in iOS apps | No | **Copied from a dashboard** | [Google](/sign-in/google/) |
| `GOOGLE_ANDROID_CLIENT_ID` | Google in Android apps | No | **Copied from a dashboard** | [Google](/sign-in/google/) |
| `APPLE_TEAM_ID` | Apple | No | **Copied from a dashboard**: Apple Developer | [Apple](/sign-in/apple/) |
| `APPLE_SERVICES_ID` | Apple on websites | No | **Chosen by you**, registered at Apple | [Apple](/sign-in/apple/) |
| `APPLE_KEY_ID` | Apple | No | **Copied from a dashboard** | [Apple](/sign-in/apple/) |
| `APPLE_PRIVATE_KEY_FILE` or `APPLE_PRIVATE_KEY` | Apple | **Yes** | **Downloaded file** (`.p8`), once | [Apple](/sign-in/apple/) |
| `APPLE_BUNDLE_IDS` | Apple in iOS apps | No | **Copied from Xcode** | [Apple](/sign-in/apple/) |

## Email

| Variable | Needed when | Secret? | Obtained |
|---|---|---|---|
| `MAIL_DELIVERY` | Only to try the real provider locally (`provider`) | No | **Default is fine**: `mailpit` in development, `provider` in production |
| `RESEND_API_KEY` | Resend, real email | **Yes** | **Copied from a dashboard**: Resend → API Keys, shown once |
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_TLS`, `SMTP_USERNAME` | SMTP, after `aps add mail --provider smtp` | No | **Copied from a dashboard**: your provider's SMTP settings |
| `SMTP_PASSWORD` | SMTP with a username | **Yes** | **Copied from a dashboard** |
| `MAILPIT_SMTP_ADDR` | Development | No | **Default is fine**: `127.0.0.1:1025` |

## Server and infrastructure

| Variable | Default | Secret? | Obtained |
|---|---|---|---|
| `APP_ENV` | `development` | No | **Chosen by you**: `production` on servers (the Docker image sets it) |
| `APP_ADDR` | `127.0.0.1:8080` | No | **Default is fine** on your computer; `0.0.0.0:8080` in containers (the Docker image sets it) |
| `APP_LOG_LEVEL` | `info` | No | **Default is fine** |
| `APP_DOCS_ENABLED` | `true` | No | **Chosen by you** |
| `APP_CORS_ORIGINS` | Empty | No | **Chosen by you**: your web frontends |
| `APP_MAX_BODY_BYTES` | `1048576` (1 MiB) | No | **Default is fine** |
| `APP_DB_MAX_CONNS` | `10` | No | **Default is fine** |
| `APP_JOB_WORKERS` | `10` | No | **Default is fine** |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Empty | No | **Copied from a dashboard** of your observability service, or set by `aps dev --observability` |
| `POSTGRES_PORT`, `MAILPIT_SMTP_PORT`, `MAILPIT_WEB_PORT`, `GRAFANA_PORT`, `OTLP_HTTP_PORT` | `5432`, `1025`, `8025`, `3000`, `4318` | No | **Default is fine**: change only when a port is taken on your computer; used by `compose.yaml`, not the app |

## Secrets from files

For any secret, you can set `NAME_FILE` to a file's path instead of `NAME`, and the app reads the value from the file: `DATABASE_URL_FILE`, `AUTH_ENCRYPTION_KEYS_FILE`, `GOOGLE_CLIENT_SECRET_FILE`, `APPLE_PRIVATE_KEY_FILE`, `RESEND_API_KEY_FILE`, `SMTP_PASSWORD_FILE`. Set one or the other: if both are set, the app refuses to start.

## A complete development `.env`

What `aps dev` writes for an app named `acme-api`, with Google and Apple added. Values in `<…>` are yours.

```bash
# ── Server ─────────────────────────────── default is fine
APP_ENV=development
APP_ADDR=127.0.0.1:8080
APP_LOG_LEVEL=info
APP_DOCS_ENABLED=true
APP_CORS_ORIGINS=                        # chosen by you: e.g. http://localhost:3000 for a local frontend
APP_MAX_BODY_BYTES=1048576
OTEL_EXPORTER_OTLP_ENDPOINT=             # optional

# ── Database ───────────────────────────── created by aps (from compose.yaml)
DATABASE_URL=postgres://acme-api:acme-api@127.0.0.1:5432/acme-api?sslmode=disable
POSTGRES_PORT=5432                       # change both this and DATABASE_URL if 5432 is taken
APP_DB_MAX_CONNS=10
APP_JOB_WORKERS=10

# ── Two-factor authentication ──────────── created by aps
AUTH_ENCRYPTION_KEYS=k1:<44 characters of base64>

# ── Passkeys ───────────────────────────── empty: localhost works
WEBAUTHN_RP_ID=
WEBAUTHN_ORIGINS=
WEBAUTHN_APPLE_APP_IDS=                  # optional: copied from Apple Developer + Xcode
WEBAUTHN_ANDROID_APPS=                   # optional: copied from Play Console / keytool

# ── Google and Apple ───────────────────── optional
APP_PUBLIC_URL=                          # empty means http://localhost:8080
GOOGLE_CLIENT_ID=<id>.apps.googleusercontent.com          # copied from Google Cloud Console
GOOGLE_CLIENT_SECRET=GOCSPX-<secret>                      # copied from Google Cloud Console (secret)
GOOGLE_IOS_CLIENT_ID=                                     # optional
GOOGLE_ANDROID_CLIENT_ID=                                 # optional
APPLE_TEAM_ID=<10 characters>                             # copied from Apple Developer
APPLE_SERVICES_ID=com.example.web                         # chosen by you, registered at Apple
APPLE_KEY_ID=<10 characters>                              # copied from Apple Developer
APPLE_PRIVATE_KEY_FILE=/Users/you/.config/acme-api/AuthKey_<KEY ID>.p8   # downloaded file (secret)
APPLE_BUNDLE_IDS=                                         # optional: your iOS app

# ── Email ──────────────────────────────── Mailpit, nothing to set
MAIL_DELIVERY=
MAILPIT_SMTP_ADDR=127.0.0.1:1025
MAILPIT_SMTP_PORT=1025
MAILPIT_WEB_PORT=8025
GRAFANA_PORT=3000
OTLP_HTTP_PORT=4318
RESEND_API_KEY=                          # only with MAIL_DELIVERY=provider
```

Web sign-in with Apple needs an https address, so on your computer it only works through a tunnel ([Apple sign-in, step 8](/sign-in/apple/#step-8-try-it)).

## A complete production environment

Set these in your hosting provider's secret settings, never in a file in your repository.

```bash
APP_ENV=production                        # the Docker image sets it
APP_ADDR=0.0.0.0:8080                     # the Docker image sets it
APP_CORS_ORIGINS=https://app.example.com  # chosen by you
APP_DOCS_ENABLED=false                    # chosen by you

DATABASE_URL=postgres://<user>:<password>@<host>:5432/<db>?sslmode=require   # copied from your database provider (secret)
AUTH_ENCRYPTION_KEYS=k1:<new key>         # generated by you (secret)
RESEND_API_KEY=re_<key>                   # copied from Resend (secret)

WEBAUTHN_RP_ID=example.com                # chosen by you
WEBAUTHN_ORIGINS=https://app.example.com  # chosen by you

APP_PUBLIC_URL=https://api.example.com    # chosen by you
GOOGLE_CLIENT_ID=<id>.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET_FILE=/run/secrets/google_client_secret   # copied from Google (secret, as a file)
APPLE_TEAM_ID=<10 characters>
APPLE_SERVICES_ID=com.example.web
APPLE_KEY_ID=<10 characters>
APPLE_PRIVATE_KEY_FILE=/run/secrets/apple_private_key          # downloaded .p8 (secret, as a file)
```

Then set the sender address, which is a runtime setting rather than a variable: `PUT /ops/settings/mail.from_email`. Before launch, go through the [go-live checklist](/sign-in/go-live/).
