How-To Guide
Create a Bootstrap Script for Application Secrets¶
Goal: Create a reusable bootstrap script that generates cryptographically secure secrets for your application and stores them in the application-secrets namespace.
Time: ~45 minutes
When to use this: You’re deploying a new application that needs multiple secrets managed via the application-secrets pattern.
Prerequisites¶
kubectl access to the cluster
application-secretsnamespace existsBasic bash scripting knowledge
Understanding of Bootstrap Application Secrets pattern
Why Create a Bootstrap Script?¶
Bootstrap scripts provide:
✅ Repeatability: Same process for all environments
✅ Security: Cryptographically secure random generation
✅ Documentation: Script documents what secrets exist
✅ Auditing: Saves generated values for password manager storage
✅ Idempotency: Can update existing secrets without recreating
Script Template Structure¶
A good bootstrap script follows this pattern:
#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, pipe failures
# 1. Banner and description
# 2. Check prerequisites
# 3. Generate random secrets
# 4. Prompt for user-provided values
# 5. Create source secrets in application-secrets namespace
# 6. Save credentials to timestamped file
# 7. Display next steps
Step 1: Create Script File¶
# Create script in your project
cd kube-hetzner/scripts/
touch bootstrap-myapp-secrets.sh
chmod +x bootstrap-myapp-secrets.sh
Step 2: Add Script Header¶
#!/bin/bash
# Bootstrap secrets for MyApp application
#
# This script generates cryptographically secure random values and creates
# source secrets in the application-secrets namespace. These secrets will be
# synced to the myapp namespace via External Secrets Operator.
#
# Usage: ./bootstrap-myapp-secrets.sh
#
# Prerequisites:
# - kubectl access to cluster
# - application-secrets namespace exists
# - Appropriate RBAC permissions
set -euo pipefail
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🔐 Bootstrapping MyApp application secrets...${NC}\n"
Step 3: Check Prerequisites¶
# Check kubectl is available
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}❌ kubectl not found. Please install kubectl.${NC}"
exit 1
fi
# Check connection to cluster
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}❌ Cannot connect to Kubernetes cluster.${NC}"
echo -e " Set KUBECONFIG or configure kubectl access."
exit 1
fi
# Check application-secrets namespace exists
if ! kubectl get namespace application-secrets &> /dev/null; then
echo -e "${RED}❌ application-secrets namespace does not exist.${NC}"
echo -e " Create it first: kubectl create namespace application-secrets"
exit 1
fi
echo -e "${GREEN}✓ Prerequisites verified${NC}\n"
Step 4: Generate Random Secrets¶
Use openssl rand for cryptographically secure random values:
echo -e "${BLUE}🎲 Generating random secrets...${NC}\n"
# Database credentials
DB_USERNAME="myapp"
DB_PASSWORD=$(openssl rand -base64 32)
DB_NAME="myapp_production"
# API keys
API_KEY=$(openssl rand -hex 32) # 64 hex characters
API_SECRET=$(openssl rand -base64 64) # 64 bytes base64-encoded
# Session secret
SESSION_SECRET=$(openssl rand -hex 64) # 128 hex characters
# Admin password
ADMIN_PASSWORD=$(openssl rand -base64 24)
echo -e "${GREEN}✓ Random secrets generated${NC}\n"
Best practices:
Use
openssl rand -base64for passwords (better character set)Use
openssl rand -hexfor API keys and tokensLength recommendations:
Passwords: 24-32 bytes (
-base64 24= 32 chars)API keys: 32-64 bytes (
-hex 32= 64 chars)Session secrets: 64 bytes (
-hex 64= 128 chars)
Step 5: Prompt for User-Provided Values¶
echo -e "${BLUE}📧 User-provided values${NC}\n"
# SMTP credentials (from environment or user input)
if [ -n "${SMTP_USERNAME:-}" ] && [ -n "${SMTP_PASSWORD:-}" ]; then
echo -e "${GREEN}✓ Using SMTP credentials from environment${NC}"
else
read -p "Enter SMTP username: " SMTP_USERNAME
read -sp "Enter SMTP password: " SMTP_PASSWORD
echo
fi
# OAuth credentials (optional)
read -p "Enter OAuth Client ID (or press Enter to generate): " OAUTH_CLIENT_ID
if [ -z "$OAUTH_CLIENT_ID" ]; then
OAUTH_CLIENT_ID=$(openssl rand -hex 16)
echo -e "${YELLOW}Generated OAuth Client ID: $OAUTH_CLIENT_ID${NC}"
fi
read -sp "Enter OAuth Client Secret (or press Enter to generate): " OAUTH_CLIENT_SECRET
echo
if [ -z "$OAUTH_CLIENT_SECRET" ]; then
OAUTH_CLIENT_SECRET=$(openssl rand -base64 32)
echo -e "${YELLOW}Generated OAuth Client Secret${NC}"
fi
echo
Step 6: Create Source Secrets¶
echo -e "${BLUE}📦 Creating source secrets in application-secrets namespace...${NC}\n"
# Secret 1: Database credentials
kubectl create secret generic myapp-db-secrets \
-n application-secrets \
--from-literal=username="$DB_USERNAME" \
--from-literal=password="$DB_PASSWORD" \
--from-literal=database="$DB_NAME" \
--dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}✓ Created myapp-db-secrets${NC}"
# Secret 2: API credentials
kubectl create secret generic myapp-api-secrets \
-n application-secrets \
--from-literal=api-key="$API_KEY" \
--from-literal=api-secret="$API_SECRET" \
--from-literal=session-secret="$SESSION_SECRET" \
--dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}✓ Created myapp-api-secrets${NC}"
# Secret 3: Admin and OAuth
kubectl create secret generic myapp-admin-secrets \
-n application-secrets \
--from-literal=admin-password="$ADMIN_PASSWORD" \
--from-literal=oauth-client-id="$OAUTH_CLIENT_ID" \
--from-literal=oauth-client-secret="$OAUTH_CLIENT_SECRET" \
--dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}✓ Created myapp-admin-secrets${NC}"
# Secret 4: SMTP credentials
kubectl create secret generic myapp-smtp-secrets \
-n application-secrets \
--from-literal=username="$SMTP_USERNAME" \
--from-literal=password="$SMTP_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}✓ Created myapp-smtp-secrets${NC}\n"
Tip
Idempotency trick: Using --dry-run=client -o yaml | kubectl apply -f - makes the script idempotent. It will create secrets if they don’t exist or update them if they do.
Step 7: Save Credentials to File¶
# Create credentials directory if it doesn't exist
CREDS_DIR="$HOME/.kup6s"
mkdir -p "$CREDS_DIR"
# Generate timestamped filename
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
CREDS_FILE="$CREDS_DIR/myapp-credentials-$TIMESTAMP.txt"
# Save all credentials
cat > "$CREDS_FILE" <<EOF
MyApp Bootstrap Credentials
Generated: $(date)
========================================
DATABASE CREDENTIALS:
Username: $DB_USERNAME
Password: $DB_PASSWORD
Database: $DB_NAME
API CREDENTIALS:
API Key: $API_KEY
API Secret: $API_SECRET
Session Key: $SESSION_SECRET
ADMIN CREDENTIALS:
Admin Password: $ADMIN_PASSWORD
OAUTH CREDENTIALS:
Client ID: $OAUTH_CLIENT_ID
Client Secret: $OAUTH_CLIENT_SECRET
SMTP CREDENTIALS:
Username: $SMTP_USERNAME
Password: $SMTP_PASSWORD
========================================
IMPORTANT:
1. Copy these credentials to your password manager NOW
2. Delete this file after saving: rm "$CREDS_FILE"
3. Never commit this file to version control
EOF
# Secure file permissions
chmod 600 "$CREDS_FILE"
echo -e "${GREEN}✅ Bootstrap complete!${NC}"
echo -e "${BLUE}🔐 Credentials saved to: $CREDS_FILE${NC}\n"
Step 8: Display Next Steps¶
echo -e "${YELLOW}⚠️ IMPORTANT NEXT STEPS:${NC}"
echo "1. Copy credentials to password manager NOW"
echo "2. Delete the credentials file: rm \"$CREDS_FILE\""
echo "3. Wait for ESO sync (30-60 seconds): watch kubectl get externalsecret -n myapp"
echo "4. Verify secrets: kubectl get secret -n myapp"
echo "5. Deploy application: kubectl apply -f manifests/"
echo
Complete Working Example¶
Here’s a complete simplified bootstrap script:
#!/bin/bash
# Bootstrap secrets for MyApp
set -euo pipefail
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🔐 Bootstrapping MyApp secrets...${NC}\n"
# Prerequisites
if ! kubectl get namespace application-secrets &> /dev/null; then
echo "Error: application-secrets namespace does not exist"
exit 1
fi
# Generate random values
echo -e "${BLUE}🎲 Generating secrets...${NC}"
DB_PASSWORD=$(openssl rand -base64 32)
API_KEY=$(openssl rand -hex 32)
ADMIN_PASSWORD=$(openssl rand -base64 24)
# User input
read -p "SMTP username: " SMTP_USER
read -sp "SMTP password: " SMTP_PASS
echo
# Create secrets
echo -e "\n${BLUE}📦 Creating secrets...${NC}"
kubectl create secret generic myapp-db-secrets \
-n application-secrets \
--from-literal=password="$DB_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic myapp-api-secrets \
-n application-secrets \
--from-literal=api-key="$API_KEY" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic myapp-smtp-secrets \
-n application-secrets \
--from-literal=username="$SMTP_USER" \
--from-literal=password="$SMTP_PASS" \
--dry-run=client -o yaml | kubectl apply -f -
# Save credentials
CREDS_FILE="$HOME/.kup6s/myapp-credentials-$(date +%Y%m%d-%H%M%S).txt"
mkdir -p "$HOME/.kup6s"
cat > "$CREDS_FILE" <<EOF
MyApp Credentials - $(date)
========================================
Database Password: $DB_PASSWORD
API Key: $API_KEY
Admin Password: $ADMIN_PASSWORD
SMTP User: $SMTP_USER
SMTP Pass: $SMTP_PASS
========================================
SAVE TO PASSWORD MANAGER, THEN DELETE THIS FILE!
EOF
chmod 600 "$CREDS_FILE"
echo -e "${GREEN}✅ Complete! Credentials saved to: $CREDS_FILE${NC}"
echo -e "${BLUE}⚠️ Save to password manager and delete the file!${NC}"
Testing Your Script¶
Test in Safe Environment First¶
# Create a test namespace
kubectl create namespace application-secrets-test
# Modify script to use test namespace
# Run script
./bootstrap-myapp-secrets.sh
# Verify secrets created
kubectl get secrets -n application-secrets-test
# Check secret contents
kubectl get secret myapp-db-secrets -n application-secrets-test -o yaml
# Cleanup
kubectl delete namespace application-secrets-test
Validation Checklist¶
✅ Script runs without errors ✅ Prerequisites are checked ✅ All secrets are created in application-secrets namespace ✅ Credentials file is created with correct permissions (600) ✅ All generated values are unique (re-run script, values should differ) ✅ Idempotent (running twice doesn’t create errors) ✅ User prompts work correctly ✅ Next steps are clearly displayed
Best Practices¶
Security¶
Never echo secrets to console (except in saved file)
# ❌ BAD echo "Password: $PASSWORD" # ✅ GOOD echo "Password: <generated>"
Use
read -spfor password prompts (silent input)read -sp "Enter password: " PASSWORD echo # New line after silent input
Secure credentials file permissions
chmod 600 "$CREDS_FILE"Remind users to delete file
echo "⚠️ Delete credentials file after saving!"
Error Handling¶
Use
set -euo pipefailat script startset -e: Exit on errorset -u: Exit on undefined variableset -o pipefail: Exit if any pipe command fails
Check prerequisites explicitly
if ! command -v kubectl &> /dev/null; then echo "Error: kubectl not found" exit 1 fi
Validate required environment variables
if [ -z "${KUBECONFIG:-}" ]; then echo "Warning: KUBECONFIG not set, using default" fi
Idempotency¶
Make scripts safe to run multiple times:
# ✅ GOOD: Idempotent creation
kubectl create secret generic myapp-secrets \
--from-literal=password="$PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
# ❌ BAD: Fails if secret exists
kubectl create secret generic myapp-secrets \
--from-literal=password="$PASSWORD"
User Experience¶
Clear progress indicators
echo -e "${BLUE}🎲 Generating secrets...${NC}" # ... work ... echo -e "${GREEN}✓ Secrets generated${NC}"
Explicit next steps
echo "Next steps:" echo "1. Do this" echo "2. Then this"
Save time with defaults
read -p "Database name [myapp_production]: " DB_NAME DB_NAME=${DB_NAME:-myapp_production}
Real-World Example: GitLab BDA¶
The GitLab BDA deployment uses a production bootstrap script:
Location: kube-hetzner/scripts/bootstrap-gitlabbda-secrets.sh
Secrets managed:
gitlabbda-app-secrets (7 keys)
gitlabbda-smtp-secrets (2 keys)
gitlabbda-harbor-secrets (4 keys)
Key features:
Generates 15+ cryptographically secure values
Prompts for SMTP credentials (from environment or user)
Creates all secrets idempotently
Saves credentials to
~/.kup6s/gitlabbda-credentials-TIMESTAMP.txtDisplays verification commands
See Reference: GitLab BDA Secrets for details.
Troubleshooting¶
“Secret already exists” error¶
Solution: Use idempotent creation with --dry-run=client -o yaml | kubectl apply -f -
Credentials file has wrong permissions¶
Solution: Ensure chmod 600 "$CREDS_FILE" is in script
Script fails on missing prerequisites¶
Solution: Add explicit checks at script start
Generated secrets are not truly random¶
Solution: Use openssl rand, not $RANDOM or /dev/urandom
Next Steps¶
Bootstrap Application Secrets - Use your script to bootstrap secrets
Reference: GitLab BDA Secrets - See production example
External Secrets Operator - Understanding ESO