Bootstrap¶
First-time setup: provision namespaces, secrets, Argo CD, and trigger initial sync.
Purpose¶
Run once per cluster lifecycle (or after full teardown + rebuild). Gets the cluster from empty DOKS to a state where Argo CD is running and all Applications are Synced/Healthy.
When to use¶
- First deploy to a new cluster
- Full cluster rebuild after teardown
- After cluster upgrade that requires re-bootstrapping
Step 1: Generate Doppler admin token¶
The Doppler Terraform provider needs a Personal Token (or Service Account token on Team/Enterprise plan)
to create and manage the oep project structure. Service Tokens are read-only and tied to a single
config - they cannot create projects or environments.
Generate a Personal Token (all plans)¶
- Log in at dashboard.doppler.com
- Click your avatar (top-right) → Personal Settings
- Select Tokens in the left sidebar
- Click Generate token → name it
terraform-oep-infra→ click Generate
Copy the token value. It is shown only once.
Generate a Service Account token (Team/Enterprise plans - preferred)¶
Service Accounts are dedicated machine identities - not tied to any person.
- Go to Workplace Settings (cog icon, top-left) → Service Accounts
- Click Add Service Account → name it
terraform-oep-infra - Set Workplace Role to Workplace Admin
- Generate a token under the service account → copy the value
For CI: add the token value as a GitHub org secret named DOPPLER_ADMIN_TOKEN.
Step 2: Generate Doppler service tokens¶
Three read-only Service Tokens, one per tenant environment. Used by ESO ClusterSecretStore to sync secrets from Doppler into Kubernetes Secrets.
- In dashboard.doppler.com, open project oep
- For each config (
oep-stg,mansety-prd,us-prd): - Click the config name → Access tab → Service Tokens → Generate
- Name:
k8s-<env>-<YYYY-MM>(e.g.k8s-oep-stg-2026-05) - Scope: Read-Only
- Copy the token (shown once)
Store each token in three places:
| Location | Name | Purpose |
|---|---|---|
GH repo secret (unipuka/soa) |
DOPPLER_SERVICE_TOKEN_OEP_STG |
Used by soa CI workflows (doppler run) |
| GH org secret | DOPPLER_SERVICE_TOKEN_OEP_STG |
Passed to TF plan/apply as TF_VAR_doppler_service_token_oep_stg |
| Doppler dashboard | (source of truth) | Rotation reference |
export TF_VAR_doppler_service_token_oep_stg="dp.st.xxxx..."
export TF_VAR_doppler_service_token_mansety_prd="dp.st.xxxx..."
export TF_VAR_doppler_service_token_us_prd="dp.st.xxxx..."
For CI: add DOPPLER_ADMIN_TOKEN, DOPPLER_SERVICE_TOKEN_OEP_STG,
DOPPLER_SERVICE_TOKEN_MANSETY_PRD, DOPPLER_SERVICE_TOKEN_US_PRD as GitHub org secrets.
These are mapped to TF_VAR_* in the reusable plan/apply workflows.
Rotation runbook: rotate-secrets.md
Step 3: Set up Cloudflare Access IdPs (first-time only)¶
Before running terraform apply for the first time, configure the Cloudflare Zero Trust Identity Providers and extend the Cloudflare API token scope. This is required for docs_site.tf to apply without errors.
See Cloudflare Access IdP Setup for full instructions.
Step 4: Create Argo CD GitHub App¶
Argo CD uses a GitHub App (not a PAT) to clone unipuka-infra-ops. GitHub Apps have
no expiry and support fine-grained repo permissions.
4.1 Create the app¶
- Go to github.com/organizations/unipuka/settings/apps/new
- Fill in:
- GitHub App name:
unipuka-argocd - Homepage URL:
https://argocd.unipuka.app - Webhook: uncheck "Active" (Argo CD uses polling, not webhooks)
- Repository permissions: Contents = Read-only, Metadata = Read-only
- Where can this be installed: Only on this account
- Click Create GitHub App
4.2 Capture App ID and generate private key¶
After creation:
- Copy the App ID from the app settings page (numeric, e.g.
12345678) - Scroll to Private keys → Generate a private key
- Download the
.pemfile
4.3 Install the app¶
- On the app settings page → Install App → install on the
unipukaorg - Select Only select repositories → choose
unipuka-infra-ops - Click Install
- After install, the URL ends with
.../installations/<installationId>- copy the number
4.4 Export variables¶
export TF_VAR_argocd_github_app_id="12345678"
export TF_VAR_argocd_github_app_installation_id="987654321"
export TF_VAR_argocd_github_app_private_key="$(cat /path/to/unipuka-argocd.pem)"
For CI: add ARGOCD_GITHUB_APP_ID, ARGOCD_GITHUB_APP_INSTALLATION_ID,
ARGOCD_GITHUB_APP_PRIVATE_KEY as GitHub org secrets.
Step 5: Run terraform apply¶
Prerequisites:
- [ ]
doctl kubernetes cluster kubeconfig save oep-prd-cluster- kubeconfig set - [ ]
doppler_admin_tokenexported (Step 1) - [ ] 3x Doppler service tokens exported (Step 2)
- [ ] Cloudflare Access IdPs configured + UUIDs in Doppler (Step 3)
- [ ]
cf_account_idfilled intenants.tf(from Step 3) - [ ] Argo CD GitHub App vars exported (Step 4)
- [ ]
terraform.tfvarspopulated (copy fromterraform.tfvars.example) - [ ]
.envrcpopulated with Spaces backend creds (direnv allow)
cd oep-infra
# Only needed first time or after provider version changes
direnv exec . terraform init
# Review changes
direnv exec . terraform plan
# Apply
direnv exec . terraform apply
Step 6: Verify¶
# Namespaces with labels
kubectl get ns --show-labels
# ESO ClusterSecretStores valid
kubectl get clustersecretstores
# DOCR pull secret present in all tenant namespaces
for ns in oep-stg mansety-prd us-prd external-secrets monitoring; do
kubectl get secret do-registry -n $ns -o name
done
# Argo CD reachable (port-forward if HTTPRoute not yet set up)
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080 in browser
# All applications healthy
argocd app list
Rollback¶
TF bootstrap is additive - existing cluster is unchanged.
- Remove specific resources:
terraform destroy -target=kubernetes_namespace.tenants - Argo CD removal:
helm uninstall argocd -n argocd
V&V¶
argocd app listshowsroot,platform- bothSynced/Healthy- Subsequent
terraform applyproduces an empty diff kubectl get nslists all 6 namespaces with labelskubectl get clustersecretstoresshows 3 stores allValid