🔐 Keep your API keys secure with enterprise-grade secrets management
Stream Daemon supports multiple secrets management solutions to keep your credentials safe and out of version control.
- Quick Start
- Supported Secrets Managers
- Priority System
- Doppler Setup
- AWS Secrets Manager Setup
- HashiCorp Vault Setup
- Docker Integration
- Testing
- Troubleshooting
- Best Practices
Use the interactive wizard to set up everything automatically:
./scripts/create-secrets.shThe wizard will:
- ✅ Guide you through choosing a secrets platform (Doppler/AWS/Vault/.env)
- ✅ Interactively collect all your credentials
- ✅ Set up secrets in your chosen platform
- ✅ Generate the appropriate
.envconfiguration file - ✅ Load existing values if re-running
📖 Complete Wizard Documentation - Full interactive setup guide
Choose your secrets manager:
| Solution | Best For | Setup Time | Cost |
|---|---|---|---|
| Doppler | Personal projects, small teams | 10 min | Free |
| AWS Secrets Manager | AWS-heavy infrastructure | 30 min | ~$0.40/secret/month |
| HashiCorp Vault | Enterprise, on-premises | 1-2 hours | Self-hosted |
| Environment Variables | Quick testing only | 2 min | Free |
Recommended: Start with Doppler - it's free, simple, and production-ready.
Why Doppler:
- ✅ Completely free for personal projects
- ✅ Beautiful UI that's actually enjoyable
- ✅ Zero infrastructure to maintain
- ✅ Excellent CLI for development
- ✅ Built-in version history and audit logs
- ✅ Multi-environment support (dev/staging/prod)
- ✅ 10-minute setup
When to use: Default choice for most users, especially solo developers and small teams.
Why AWS:
- ✅ Deep AWS ecosystem integration
- ✅ IAM role-based access (no credentials needed on EC2/ECS)
- ✅ Automatic rotation capabilities
- ✅ Enterprise compliance features
When to use: Already heavily invested in AWS, running on EC2/ECS/Lambda.
Why Vault:
- ✅ On-premises deployment
- ✅ Advanced policy-based access control
- ✅ Dynamic secrets generation
- ✅ Extensive audit logging
When to use: Enterprise environments, complex policy requirements, self-hosted infrastructure.
Stream Daemon uses a priority-based system for loading secrets:
1. Secrets Manager (Doppler/AWS/Vault) ← HIGHEST PRIORITY
2. Environment Variables (.env file) ← FALLBACK
3. None (error if not found)
Secrets managers override environment variables:
- If a secret exists in Doppler, the
.envvalue is ignored - Allows safe development: keep dev credentials in
.env, production in secrets manager - No accidental production credential commits to git
What gets overridden:
- API keys:
YOUTUBE_API_KEY,GEMINI_API_KEY - OAuth credentials:
TWITCH_CLIENT_ID,TWITCH_CLIENT_SECRET - Access tokens:
MASTODON_ACCESS_TOKEN,MATRIX_ACCESS_TOKEN - App passwords:
BLUESKY_APP_PASSWORD - Webhooks:
DISCORD_WEBHOOK_URL
What stays in .env (not secrets):
- Platform enables:
TWITCH_ENABLE,MASTODON_ENABLE - Usernames:
TWITCH_USERNAME,YOUTUBE_USERNAME - Public URLs:
MASTODON_INSTANCE_URL,MATRIX_HOMESERVER - Intervals:
SETTINGS_CHECK_INTERVAL,SETTINGS_POST_INTERVAL - Secrets manager config:
SECRETS_MANAGER,DOPPLER_TOKEN
.env file:
TWITCH_ENABLE=True
TWITCH_USERNAME=chiefgyk3d
TWITCH_CLIENT_ID=dev_client_id_12345 # ← IGNORED if in Doppler
TWITCH_CLIENT_SECRET=dev_client_secret_67890 # ← IGNORED if in Doppler
SECRETS_MANAGER=doppler
DOPPLER_TOKEN=dp.st.dev.xxxxxDoppler secrets:
TWITCH_CLIENT_ID=prod_client_id_abcxyz
TWITCH_CLIENT_SECRET=prod_client_secret_qwertyResult:
- ✅ Uses Doppler's
prod_client_id_abcxyz(overrides.env) - ✅ Uses Doppler's
prod_client_secret_qwerty(overrides.env) - ✅ Uses
.envforTWITCH_USERNAME(configuration, not secret)
Pro Tip: Comment out sensitive credentials in .env to avoid confusion:
# TWITCH_CLIENT_ID=dev_client_id_12345 ← Commented = clear it's not used
# TWITCH_CLIENT_SECRET=dev_client_secret ← Commented = clear it's not usedOption 1: Use the Interactive Wizard ⭐
./scripts/create-secrets.sh
# Select "Doppler" when promptedThe wizard will guide you through the entire setup process automatically.
Option 2: Manual Setup (if you prefer doing it yourself)
My personal take: After trying AWS Secrets Manager, Vault, and various solutions, Doppler is the sweet spot for Stream Daemon:
- Free forever - No credit card, unlimited secrets for personal use
- Dead simple - Sign up → Add secrets → Get token → Done
- No infrastructure - Unlike Vault (servers) or AWS (account complexity)
- Great UX - Best-in-class web UI and CLI
- Version history - See every change, rollback anytime
- Team-friendly - Easy sharing without complex IAM policies
- Go to doppler.com
- Sign up (GitHub, Google, or email - no credit card)
- Create project: "stream-daemon"
- You're automatically in the
devenvironment
Environments: Doppler organizes secrets by environment:
dev- Development (default, perfect for most users)stg- Staging (optional)prd- Production (optional)
For solo streamers, just use dev. Create additional environments only if you need separate credentials for testing vs production.
Click "Add Secret" for each platform you're using. Make sure you're in the dev environment (check dropdown).
🎯 Naming Convention: Use UPPERCASE with underscores. Secret names must start with the prefix you configure.
Twitch:
TWITCH_CLIENT_ID=your_client_id_here
TWITCH_CLIENT_SECRET=your_client_secret_hereGet from: Twitch Developer Console
YouTube:
YOUTUBE_API_KEY=AIzaSyABC123XYZ789...Get from: Google Cloud Console
Kick (optional):
KICK_CLIENT_ID=your_kick_client_id
KICK_CLIENT_SECRET=your_kick_secretGet from: Kick Settings → Developer (requires 2FA)
Mastodon:
MASTODON_CLIENT_ID=your_client_id
MASTODON_CLIENT_SECRET=your_client_secret
MASTODON_ACCESS_TOKEN=your_access_tokenGet from: Instance Settings → Development → New Application
Bluesky:
BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxxGet from: Bluesky App Passwords
Discord:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/123/abc...
# Optional: Platform-specific webhooks
DISCORD_WEBHOOK_TWITCH=https://discord.com/api/webhooks/456/def...
DISCORD_WEBHOOK_YOUTUBE=https://discord.com/api/webhooks/789/ghi...Get from: Discord Server Settings → Integrations → Webhooks
Matrix:
MATRIX_HOMESERVER=https://matrix.org
MATRIX_ACCESS_TOKEN=syt_abc123...
MATRIX_ROOM_ID=!AbCdEfGhIjKl:matrix.org
# OR use username/password (takes priority over token)
MATRIX_USERNAME=@streambot:matrix.org
MATRIX_PASSWORD=your_matrix_passwordGet from: Element → Settings → Help & About → Access Token
AI Messages (optional):
GEMINI_API_KEY=AIzaSyABC123XYZ789...Get from: Google AI Studio
You need a token to authenticate Stream Daemon with Doppler.
dev token only accesses dev secrets. A prd token only accesses prd secrets.
Option A: Service Token (Recommended for Production)
- In Doppler dashboard → Access → Service Tokens
- Generate Service Token
- Name:
stream-daemon-dev(orstream-daemon-prdfor production) - Environment: Select
dev(or whichever you're using) - Copy the token (starts with
dp.st.dev.ordp.st.prd.) - Save it - you can't see it again!
Option B: CLI + Personal Token (Best for Development)
# Install Doppler CLI
# macOS:
brew install dopplerhq/cli/doppler
# Linux:
curl -Ls https://cli.doppler.com/install.sh | sh
# Windows (PowerShell):
iwr -useb https://cli.doppler.com/install.ps1 | iex
# Login
doppler login
# Setup in project directory
cd /path/to/stream-daemon
doppler setup
# Select: stream-daemon → dev
# Run without manually setting DOPPLER_TOKEN
doppler run -- python3 stream-daemon.pyEdit your .env file:
# =====================================
# SECRETS MANAGER
# =====================================
SECRETS_MANAGER=doppler
DOPPLER_TOKEN=dp.st.dev.your_token_here_abc123xyz
# Secret name prefix (must match Doppler secret names)
# Example: SECRETS_DOPPLER_TWITCH_SECRET_NAME=TWITCH means
# secrets in Doppler must be: TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET
SECRETS_DOPPLER_TWITCH_SECRET_NAME=TWITCH
SECRETS_DOPPLER_YOUTUBE_SECRET_NAME=YOUTUBE
SECRETS_DOPPLER_KICK_SECRET_NAME=KICK
SECRETS_DOPPLER_MASTODON_SECRET_NAME=MASTODON
SECRETS_DOPPLER_BLUESKY_SECRET_NAME=BLUESKY
SECRETS_DOPPLER_DISCORD_SECRET_NAME=DISCORD
SECRETS_DOPPLER_MATRIX_SECRET_NAME=MATRIX
SECRETS_DOPPLER_GEMINI_SECRET_NAME=GEMINI
# =====================================
# PLATFORM CONFIGURATION (non-sensitive)
# =====================================
TWITCH_ENABLE=True
TWITCH_USERNAME=your_username
YOUTUBE_ENABLE=True
YOUTUBE_USERNAME=@YourHandle
MASTODON_ENABLE=True
MASTODON_INSTANCE_URL=https://mastodon.social
# =====================================
# COMMENT OUT CREDENTIALS! ❌
# =====================================
# These are now in Doppler, comment them out:
# TWITCH_CLIENT_ID=...
# TWITCH_CLIENT_SECRET=...
# YOUTUBE_API_KEY=...
# MASTODON_ACCESS_TOKEN=...
# BLUESKY_APP_PASSWORD=...
# DISCORD_WEBHOOK_URL=...When configured with SECRETS_DOPPLER_TWITCH_SECRET_NAME=TWITCH:
- Stream Daemon connects to Doppler API
- Fetches all secrets starting with
TWITCH_ - Strips the prefix:
TWITCH_CLIENT_ID→CLIENT_ID - Returns credentials dict to platform
Matching logic:
TWITCH_CLIENT_ID✅ Matches (starts withTWITCH_)TWITCH_CLIENT_SECRET✅ Matchestwitch_client_id❌ Case-sensitive (use UPPERCASE)YOUTUBE_API_KEY❌ Different prefix
What are environments?
- Separate sets of secrets within one project
- Common setup:
dev(development),stg(staging),prd(production) - Each environment has its own token
- Tokens can't cross-access environments (security feature)
Example workflow:
# Development
DOPPLER_TOKEN=dp.st.dev.xxxx # Accesses dev environment
# Uses dev Twitch app, dev Discord webhook, test credentials
# Production
DOPPLER_TOKEN=dp.st.prd.yyyy # Accesses prd environment
# Uses production Twitch app, real Discord webhook, live credentialsWhen to use multiple environments:
- ✅ Testing new integrations without breaking production
- ✅ Different webhooks for test vs production
- ✅ Team collaboration with separated access
- ❌ Solo streamer with one set of credentials (just use
dev)
docker-compose.yml:
version: '3.8'
services:
stream-daemon:
build: .
environment:
# Secrets Manager
SECRETS_MANAGER: doppler
DOPPLER_TOKEN: ${DOPPLER_TOKEN} # From host .env
# Secret prefixes
SECRETS_DOPPLER_TWITCH_SECRET_NAME: TWITCH
SECRETS_DOPPLER_YOUTUBE_SECRET_NAME: YOUTUBE
# Platform config (non-sensitive)
TWITCH_ENABLE: 'True'
TWITCH_USERNAME: your_username
volumes:
- ./messages.txt:/app/messages.txtHost .env (for docker-compose):
DOPPLER_TOKEN=dp.st.dev.your_token_hereRun:
docker-compose up -dOr use Doppler CLI:
doppler run -- docker-compose up -dOption 1: Use the Interactive Wizard ⭐
./scripts/create-secrets.sh
# Select "AWS Secrets Manager" when promptedThe wizard will create all secrets in AWS Secrets Manager automatically.
Option 2: Manual Setup (if you prefer doing it yourself)
- Already running on EC2, ECS, or Lambda
- Need IAM role-based access (no credentials in containers)
- Enterprise compliance requires AWS
- Want automatic credential rotation
# Install AWS CLI
pip install awscli
# Configure credentials
aws configure
# Create secret for Twitch
aws secretsmanager create-secret \
--name stream-daemon/twitch \
--description "Twitch API credentials" \
--secret-string '{
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}'
# Create secret for YouTube
aws secretsmanager create-secret \
--name stream-daemon/youtube \
--secret-string '{
"api_key": "AIzaSyABC123XYZ789..."
}'
# Create secret for Discord
aws secretsmanager create-secret \
--name stream-daemon/discord \
--secret-string '{
"webhook_url": "https://discord.com/api/webhooks/123/abc..."
}'# .env
SECRETS_MANAGER=aws
AWS_REGION=us-east-1
# Secret names in AWS
SECRETS_AWS_TWITCH_SECRET_NAME=stream-daemon/twitch
SECRETS_AWS_YOUTUBE_SECRET_NAME=stream-daemon/youtube
SECRETS_AWS_DISCORD_SECRET_NAME=stream-daemon/discord
# AWS credentials (or use IAM role)
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYCreate policy allowing secret access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:stream-daemon/*"
}
]
}Attach to:
- IAM user (if using access keys)
- EC2 instance role (if running on EC2)
- ECS task role (if running on ECS)
docker-compose.yml:
version: '3.8'
services:
stream-daemon:
build: .
environment:
SECRETS_MANAGER: aws
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
SECRETS_AWS_TWITCH_SECRET_NAME: stream-daemon/twitchOn EC2/ECS: Skip AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY - use IAM roles instead!
Option 1: Use the Interactive Wizard ⭐
./scripts/create-secrets.sh
# Select "HashiCorp Vault" when promptedThe wizard will store all secrets in your Vault instance automatically.
Option 2: Manual Setup (if you prefer doing it yourself)
- On-premises or self-hosted infrastructure
- Complex policy requirements
- Need dynamic secrets (auto-rotation)
- Enterprise audit logging requirements
# Start Vault server (development mode)
vault server -dev
# Set environment variables
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='your_vault_token'
# Enable kv secrets engine
vault secrets enable -path=secret kv-v2# Twitch credentials
vault kv put secret/stream-daemon/twitch \
client_id="your_client_id" \
client_secret="your_client_secret"
# YouTube credentials
vault kv put secret/stream-daemon/youtube \
api_key="AIzaSyABC123XYZ789..."
# Discord webhook
vault kv put secret/stream-daemon/discord \
webhook_url="https://discord.com/api/webhooks/123/abc..."# .env
SECRETS_MANAGER=vault
SECRETS_VAULT_URL=http://127.0.0.1:8200
SECRETS_VAULT_TOKEN=your_vault_token
# Secret paths in Vault
SECRETS_VAULT_TWITCH_SECRET_PATH=secret/data/stream-daemon/twitch
SECRETS_VAULT_YOUTUBE_SECRET_PATH=secret/data/stream-daemon/youtube
SECRETS_VAULT_DISCORD_SECRET_PATH=secret/data/stream-daemon/discordNote: Path includes /data/ for KV v2 engine (secret/data/path).
❌ Don't: Put secrets in Dockerfile or docker-compose.yml ✅ Do: Use environment variables or Docker secrets
version: '3.8'
services:
stream-daemon:
image: stream-daemon:latest
environment:
SECRETS_MANAGER: doppler
DOPPLER_TOKEN: ${DOPPLER_TOKEN}
# All other secrets fetched from Dopplerversion: '3.8'
services:
stream-daemon:
image: stream-daemon:latest
environment:
SECRETS_MANAGER: aws
AWS_REGION: us-east-1
# No AWS credentials - uses IAM roleversion: '3.8'
services:
stream-daemon:
image: stream-daemon:latest
secrets:
- doppler_token
environment:
SECRETS_MANAGER: doppler
DOPPLER_TOKEN_FILE: /run/secrets/doppler_token
secrets:
doppler_token:
external: trueStream Daemon includes tests to validate secrets management:
python3 tests/test_doppler_all.py# Twitch
python3 tests/test_doppler_twitch.py
# YouTube
python3 tests/test_doppler_youtube.py
# Discord
python3 tests/test_discord.py- ✅ Secrets manager connection
- ✅ Secret fetching with correct prefixes
- ✅ Credential masking (security)
- ✅ Platform authentication
- ✅ Priority system (secrets override .env)
=== Testing Doppler Integration ===
✅ Doppler connection successful
✅ Fetched Twitch secrets (client_id: xxxx...yyyy)
✅ Twitch authentication successful
✅ All secrets properly masked in logs
🎉 All tests passed!
Problem: Stream Daemon can't find secrets
Solutions:
-
Verify secret names match configured prefix:
# Config says TWITCH, so secrets must be: SECRETS_DOPPLER_TWITCH_SECRET_NAME=TWITCH # Secrets in Doppler: TWITCH_CLIENT_ID ✅ TWITCH_CLIENT_SECRET ✅
-
Check you're in the correct environment (Doppler)
-
Verify token hasn't expired or been revoked
-
Test connection:
python3 -c "from doppler_sdk import DopplerSDK; \ sdk = DopplerSDK(); \ sdk.set_access_token('YOUR_TOKEN'); \ print(sdk.secrets.list())"
Problem: Secrets manager not being called
Solution: Comment out ALL credentials in .env:
# ❌ This will be used instead of Doppler:
TWITCH_CLIENT_ID=abc123
# ✅ This allows Doppler to be used:
# TWITCH_CLIENT_ID=abc123Verify with:
grep -E "^(TWITCH_CLIENT_ID|YOUTUBE_API_KEY)" .env
# Should return nothing (or only commented lines)Problem: Token can't access expected secrets
Cause: Token is for dev environment but trying to access prd secrets
Solution:
- Check token prefix:
dp.st.dev.xxx= dev environment - In Doppler dashboard, verify which environment you're viewing
- Generate new token for correct environment if needed
Problem: "Access Denied" from AWS
Solutions:
- Verify IAM permissions include
secretsmanager:GetSecretValue - Check secret ARN in policy matches actual secret
- If using EC2/ECS, verify instance/task role is attached
- Test credentials:
aws secretsmanager get-secret-value \ --secret-id stream-daemon/twitch
Problem: Can't connect to Vault
Solutions:
- Verify Vault server is running:
vault status - Check
SECRETS_VAULT_URLis correct - Verify network connectivity:
curl $SECRETS_VAULT_URL/v1/sys/health - Check Vault token is valid:
vault token lookup
✅ Use doppler run for local development
doppler run -- python3 stream-daemon.py✅ Keep .env in .gitignore
.env
.env.local
.env.*.local✅ Use dev environment in Doppler
- Separate testing credentials from production
- Safe to experiment without breaking production
✅ Share Doppler project access with team
- Easier than passing credentials manually
- Built-in audit logging
✅ Use separate environments (prd, staging)
# Production deployment
DOPPLER_TOKEN=dp.st.prd.xxx # Production token✅ Use service tokens, not personal tokens
- Service tokens are scoped to one environment
- Easier to rotate without affecting team
✅ Rotate tokens periodically
- Doppler: Generate new service token, update deployment, revoke old
- AWS: Use automatic rotation features
- Vault: Use dynamic secrets with TTL
✅ Use IAM roles on cloud platforms
# ECS task definition
taskRoleArn: arn:aws:iam::123456789012:role/stream-daemon-task-role
# No AWS credentials needed in environment!✅ Enable audit logging
- Doppler: Automatic audit logs
- AWS: Enable CloudTrail
- Vault: Enable audit device
✅ Never commit tokens to git
# Bad
DOPPLER_TOKEN=dp.st.dev.abc123xyz # In .env, committed to git
# Good
DOPPLER_TOKEN=dp.st.dev.abc123xyz # In .env.local, in .gitignore✅ Use read-only permissions where possible
- Doppler service tokens are read-only by default ✅
- AWS IAM: Only grant
GetSecretValue, notPutSecretValue - Vault: Use restrictive policies
✅ Revoke old tokens when rotating
- Doppler: Revoke old service token after deploying new one
- AWS: Delete old IAM access keys
- Vault: Revoke old token after issuing new one
✅ Use different projects/paths for different environments
# Doppler: Separate environments
dev environment: dp.st.dev.xxx
prd environment: dp.st.prd.yyy
# AWS: Separate paths
stream-daemon-dev/twitch
stream-daemon-prd/twitch
# Vault: Separate paths
secret/dev/stream-daemon/twitch
secret/prd/stream-daemon/twitch✅ Enable 2FA on secrets manager accounts
- Doppler: Enable 2FA in account settings
- AWS: Enable MFA for root and IAM users
- Vault: Use AppRole or OIDC authentication
- Doppler Documentation - Official Doppler docs
- AWS Secrets Manager - AWS documentation
- HashiCorp Vault - Vault documentation
- Platform Setup Guides - How to get API credentials
- Docker Deployment - Docker and docker-compose examples
Having issues with secrets management?
- 📖 Check Troubleshooting above
- 🐛 Open an issue
- 💬 Ask in discussions