> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended**:
> ```bash
> npx @scalekit-inc/cli setup
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for 40+ agents.
> Features: full-stack-auth, agent-auth, mcp-auth, modular-sso, modular-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Secrets setup script

Interactive script that generates all Kubernetes secrets and a values.yaml for your Scalekit deployment.
You will run a one-time interactive script that generates all Kubernetes secrets and a values.yaml file for your deployment. This helps you bootstrap correctly without manually creating secrets or making copy-paste errors in configuration.

The script is a **one-time tool for initial deployment**. Run it once to bootstrap your cluster. Do not run it again on an existing installation.

It collects your configuration interactively and produces two output files:

- **A secrets script** (`scalekit-secrets-<env>-<timestamp>.sh`): runs `kubectl` commands to create all five required Kubernetes secrets
- **A values file** (`values-<env>-<timestamp>.yaml`): paste this into the Scalekit distribution portal when creating your deployment

## Prerequisites

| Tool | Version | Purpose |
|------|---------|---------|
| `bash` | 4.0 or later | Run the script |
| `openssl` | Any modern version | Generate cryptographic keys and tokens |
| `python3` | 3.6 or later | Generate webhook JWT and OIDC client secret |
| `kubectl` | 1.27 or later | Create Kubernetes secrets in your cluster |

`kubectl` must be configured and pointed at the cluster you are deploying to before you run the script.

On macOS, `bash` ships as version 3. Install a newer version with Homebrew:

```bash
brew install bash
```

## Run the setup script

> caution: Run this script only once
>
> The script auto-generates cryptographic keys (OIDC master key, cookie encryption keys, webhook JWT secret, and others). These are written into your Kubernetes secrets and used by the running system. If you run the script again, it generates different values and overwrites the secrets, which will break your existing deployment.
>
> To update a single credential (for example, to rotate a database password), edit the specific Kubernetes secret directly rather than re-running the script.

Copy the script below, save it as `setup-secrets.sh`, make it executable, then run it:

```bash
chmod +x setup-secrets.sh
bash setup-secrets.sh
```

When prompted to choose an environment, enter the number that matches your target:

| Option | Environment | Notes |
|--------|-------------|-------|
| `1` | Minikube (local) | Uses nginx ingress; sets `http` protocol and `allow_insecure: true` |
| `2` | GCP / GKE | Configures GKE Gateway API and NEG annotations |
| `3` | Other Kubernetes cluster | Generic config. You add your own ingress or gateway. |
| `4` | Evaluation | Fast path: Helm spins up bundled PostgreSQL and Redis; only asks for webhook and registry credentials |

### Evaluation mode

Option `4` is a shortcut for a local or throwaway environment. The script asks only for a webhook JWT secret, a webhook API token, and a registry access token, then exits. It generates a minimal `values-eval-<timestamp>.yaml` to paste into the distribution portal. No databases or Redis instances are needed. The chart provides bundled ones.

**Do not use evaluation mode in production.** The bundled databases have no backups, no replication, and no persistent storage guarantees.

### Optional flags

| Flag | Effect |
|------|--------|
| `--enable-openfga` | Includes OpenFGA secrets and database configuration |
| `--change-defaults` | Prompts you to confirm or override default values instead of accepting them silently |

## What the script collects

### Full setup (options 1, 2, 3)

The script walks through these sections:

| Section | What it asks |
|---------|-------------|
| **Namespace** | Kubernetes namespace to deploy into |
| **Environment** | Deployment target: Minikube, GCP/GKE, other K8s, or Evaluation |
| **PostgreSQL** | Host, port, credentials, and database names (scalekit, webhooks, openfga if enabled) |
| **Redis** | Host, port, password, and db indexes for app, background jobs, and webhooks |
| **Email (SMTP)** | From address, host, port, username, and password |
| **Container registry** | Registry token and server URL from the Scalekit distribution portal |
| **GKE Gateway** | GatewayClass name and GCP certificate map (GCP/GKE only) |
| **App settings** | Domain, region, replica count |
| **Admin user** | First name, last name, email for the initial dashboard login |

All cryptographic values (OIDC keys, cookie keys, webhook JWT, etc.) are auto-generated. You do not supply these.

### Evaluation mode (option 4)

The script only asks for:

| Section | What it asks |
|---------|-------------|
| **Namespace** | Kubernetes namespace to deploy into |
| **Webhook credentials** | JWT secret and API token |
| **Container registry** | Registry access token |

## After the script completes

The script prints the paths to both generated files. Before proceeding:

1. Run the secrets script to create all Kubernetes secrets:
   ```bash
   bash scalekit-secrets-<env>-<timestamp>.sh
   ```
2. Paste the contents of `values-<env>-<timestamp>.yaml` into the Scalekit distribution portal when creating or updating your deployment. See [Install Scalekit](/self-hosted/installation/) for the full portal flow.

> caution: Check databases exist first
>
> The script reminds you to verify that all three PostgreSQL databases exist before connecting your cluster. The migration hook will fail if any database is missing.

## Script

```bash title="setup-secrets.sh"
#!/usr/bin/env bash
set -euo pipefail

# ── Arguments ─────────────────────────────────────────────────────────────────
# Usage: bash setup-secrets.sh [--enable-openfga] [--change-defaults]
OPENFGA_ENABLED="false"
CHANGE_DEFAULTS="false"
for arg in "$@"; do
  case "$arg" in
    --enable-openfga)  OPENFGA_ENABLED="true" ;;
    --change-defaults) CHANGE_DEFAULTS="true" ;;
    *) echo "Unknown argument: $arg"; exit 1 ;;
  esac
done

# ── Colours ──────────────────────────────────────────────────────────────────
BOLD=$'\033[1m'
DIM=$'\033[2m'
RED=$'\033[31m'
GREEN=$'\033[32m'
YELLOW=$'\033[33m'
CYAN=$'\033[36m'
RESET=$'\033[0m'

header()  { echo "\n${BOLD}${CYAN}▶ $*${RESET}"; }
prompt()  { echo "${YELLOW}$*${RESET}"; }
success() { echo "${GREEN}✓ $*${RESET}"; }
dim()     { echo "${DIM}$*${RESET}"; }

ask() {
  local var="$1" msg="$2" default="${3:-}"
  # If a default exists and --change-defaults is not set, use it silently
  if [[ -n "$default" && "$CHANGE_DEFAULTS" == "false" ]]; then
    eval "$var=\"$default\""
    dim "  $msg = $default (default)"
    return
  fi
  while true; do
    if [[ -n "$default" ]]; then
      read -rp "${YELLOW}$msg [${default}]: ${RESET}" input
    else
      read -rp "${YELLOW}$msg: ${RESET}" input
    fi
    if [[ -z "$input" && -n "$default" ]]; then
      eval "$var=\"$default\""
      break
    elif [[ -n "$input" ]]; then
      eval "$var=\"$input\""
      break
    else
      echo "${RED}  ✗ This field is required. Please enter a value.${RESET}"
    fi
  done
}

ask_secret() {
  local var="$1" msg="$2" default="${3:-}"
  # If the 3rd argument was explicitly passed (even as ""), empty input is allowed
  local allow_empty="${3+yes}"
  while true; do
    if [[ -n "$default" ]]; then
      read -rp "${YELLOW}$msg [${default}]: ${RESET}" input
    else
      read -rp "${YELLOW}$msg: ${RESET}" input
    fi
    if [[ -z "$input" && -n "$default" ]]; then
      eval "$var=\"$default\""
      break
    elif [[ -z "$input" && "$allow_empty" == "yes" ]]; then
      eval "$var=\"\""
      break
    elif [[ -n "$input" ]]; then
      eval "$var=\"$input\""
      break
    else
      echo "${RED}  ✗ This field is required. Please enter a value.${RESET}"
    fi
  done
}

# ── Step 1: Namespace & environment ──────────────────────────────────────────
header "Step 1 — Namespace & environment"
ask NAMESPACE "Kubernetes namespace to deploy Scalekit into"
echo
echo -e "${YELLOW}Which environment are you deploying to?${RESET}"
echo "  1) Minikube (local)"
echo "  2) GCP / GKE"
echo "  3) Other Kubernetes cluster"
echo "  4) Evaluation (quickstart — Helm brings up PostgreSQL & Redis)"
read -rp "${YELLOW}Enter 1, 2, 3 or 4: ${RESET}" ENV_CHOICE
if [[ "$ENV_CHOICE" == "1" ]]; then
  ENV_LABEL="minikube"
elif [[ "$ENV_CHOICE" == "2" ]]; then
  ENV_LABEL="gke"
elif [[ "$ENV_CHOICE" == "4" ]]; then
  ENV_LABEL="eval"
else
  ENV_LABEL="k8s"
fi

# ── Evaluation flow (early exit) ──────────────────────────────────────────────
if [[ "$ENV_CHOICE" == "4" ]]; then
  header "Step 2 — Evaluation setup"
  dim "  Helm will spin up PostgreSQL and Redis automatically."
  dim "  You only need a Svix API token and registry credentials."
  echo

  ask_secret SVIX_JWT_SECRET   "  Svix JWT secret (must be the secret used to sign the API token)"
  ask_secret SVIX_API_KEY      "  Svix API token (JWT signed with the above secret)"
  ask_secret REGISTRY_PASSWORD "  Registry access token"
  echo

  VALUES_FILE="$(pwd)/values-eval-$(date +%Y%m%d%H%M%S).yaml"
  cat > "$VALUES_FILE" <}"
echo "  redis.db (main)           = $REDIS_DB"
echo "  redis.db (asynq)          = $ASYNQ_REDIS_DB"
echo "  redis.db (svix)           = $SVIX_REDIS_DB  →  $SVIX_REDIS_DSN"
echo "  email_key                 = na (fixed)"
echo "  smtp password             = $SMTP_PASSWORD"
echo "  sendgrid_key              = na (fixed)"
echo "  smtp from                 = $EMAIL_FROM_NAME <$EMAIL_FROM>"
echo "  smtp host:port            = $SMTP_HOST:$SMTP_PORT"
echo "  smtp username             = $SMTP_USERNAME"
echo "  app.domain                = $APP_DOMAIN"
echo "  app.region                = $APP_REGION"
echo "  app.protocol              = $APP_PROTOCOL"
echo "  replicaCount              = $REPLICA_COUNT"
echo "  adminUser                 = $ADMIN_FIRST_NAME $ADMIN_LAST_NAME <$ADMIN_EMAIL>"
echo "  registry_server           = $REGISTRY_SERVER"
echo "  registry_password         = $REGISTRY_PASSWORD"
echo

# ── Step 6: Write secrets script ─────────────────────────────────────────────
OUTPUT_FILE="$(pwd)/scalekit-secrets-${ENV_LABEL}-$(date +%Y%m%d%H%M%S).sh"

cat > "$OUTPUT_FILE" <> "$OUTPUT_FILE" < "$VALUES_FILE" < "$VALUES_FILE" < "$VALUES_FILE" <> /etc/hosts'"
  echo
  dim "     Or add manually — open /etc/hosts and append this line:"
  echo "     127.0.0.1  app.${APP_DOMAIN}  auth.${APP_DOMAIN}"
  echo
  dim "  5. Update CoreDNS so pods inside the cluster can resolve app.${APP_DOMAIN} and auth.${APP_DOMAIN}:"
  echo
  dim "     Open the CoreDNS ConfigMap and find the IP already assigned to host.minikube.internal."
  dim "     Add two more entries pointing to that same IP:"
  echo
  echo "        kubectl edit configmap coredns -n kube-system"
  echo
  echo "        # Inside the hosts { } block, add:"
  echo "        <host.minikube.internal IP>  app.${APP_DOMAIN}"
  echo "        <host.minikube.internal IP>  auth.${APP_DOMAIN}"
  echo
  dim "     Then restart CoreDNS to apply:"
  echo "        kubectl rollout restart deployment coredns -n kube-system"
  echo
  echo "${BOLD}${CYAN}└─────────────────────────────────────────────────────────────┘${RESET}"
fi
```

Once both files are on disk, you have everything needed to install Scalekit. Next, [Installation](/self-hosted/installation/) covers applying the secrets script and creating the deployment through the portal.


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
