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-secrets namespace exists

  • Basic 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 -base64 for passwords (better character set)

  • Use openssl rand -hex for API keys and tokens

  • Length 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

  1. Never echo secrets to console (except in saved file)

    # ❌ BAD
    echo "Password: $PASSWORD"
    
    # ✅ GOOD
    echo "Password: <generated>"
    
  2. Use read -sp for password prompts (silent input)

    read -sp "Enter password: " PASSWORD
    echo  # New line after silent input
    
  3. Secure credentials file permissions

    chmod 600 "$CREDS_FILE"
    
  4. Remind users to delete file

    echo "⚠️  Delete credentials file after saving!"
    

Error Handling

  1. Use set -euo pipefail at script start

    • set -e: Exit on error

    • set -u: Exit on undefined variable

    • set -o pipefail: Exit if any pipe command fails

  2. Check prerequisites explicitly

    if ! command -v kubectl &> /dev/null; then
        echo "Error: kubectl not found"
        exit 1
    fi
    
  3. 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

  1. Clear progress indicators

    echo -e "${BLUE}🎲 Generating secrets...${NC}"
    # ... work ...
    echo -e "${GREEN}✓ Secrets generated${NC}"
    
  2. Explicit next steps

    echo "Next steps:"
    echo "1. Do this"
    echo "2. Then this"
    
  3. 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.txt

  • Displays 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