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 |
| 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 |
| 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 |
RESEND_API_KEY (or the SMTP_ values) |
No: Mailpit is used | Yes | Yes | Copied from a dashboard, see Email sending |
Generate a production encryption key:
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 |
WEBAUTHN_ORIGINS |
Passkeys in production | No | Chosen by you: your https frontends | Passkeys |
WEBAUTHN_APPLE_APP_IDS |
Passkeys in iOS apps | No | Copied from a dashboard: Team ID + bundle ID | Mobile passkeys |
WEBAUTHN_ANDROID_APPS |
Passkeys in Android apps | No | Copied from a dashboard: package name + SHA-256 fingerprints | Mobile passkeys |
APP_PUBLIC_URL |
Google or Apple on the web, in production | No | Chosen by you: your API's https address | |
GOOGLE_CLIENT_ID |
No | Copied from a dashboard: Google Cloud Console | ||
GOOGLE_CLIENT_SECRET |
Yes | Copied from a dashboard, shown when the client is created | ||
GOOGLE_IOS_CLIENT_ID |
Google in iOS apps | No | Copied from a dashboard | |
GOOGLE_ANDROID_CLIENT_ID |
Google in Android apps | No | Copied from a dashboard | |
APPLE_TEAM_ID |
Apple | No | Copied from a dashboard: Apple Developer | Apple |
APPLE_SERVICES_ID |
Apple on websites | No | Chosen by you, registered at Apple | Apple |
APPLE_KEY_ID |
Apple | No | Copied from a dashboard | Apple |
APPLE_PRIVATE_KEY_FILE or APPLE_PRIVATE_KEY |
Apple | Yes | Downloaded file (.p8), once |
Apple |
APPLE_BUNDLE_IDS |
Apple in iOS apps | No | Copied from Xcode | 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.
# ── 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).
A complete production environment#
Set these in your hosting provider's secret settings, never in a file in your repository.
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.