New Project — Secrets & CI/CD Variables Setup
Audience: Any engineer (or Claude Code session) bootstrapping a new project that needs a GitLab CI/CD pipeline deploying to Kubernetes.
This guide covers the exact commands to run from each of the three environments we use — Windows (Git Bash / PowerShell), WSL2, and Claude Desktop — to:
- Create an Infisical project and seed application secrets
- Set the required GitLab CI/CD variables (
KUBE_CONFIG) - Verify the pipeline can authenticate to the registry and cluster
Nothing is ever committed to git. All secrets live in Infisical and are synced to K8s via InfisicalSecret CR.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ SECRET SOURCES │
│ │
│ Infisical (anshin-knowledge project) │
│ └── KNOWLEDGE_JWT_SECRET, LDAP_URL, LDAP_BASE_DN, … │
│ │ │
│ ▼ InfisicalSecret CR (k8s/base/infisical-secret.yaml)│
│ K8s Secret "knowledge-secrets" (namespace: knowledge) │
│ │ │
│ ▼ envFrom: [{secretRef: {name: knowledge-secrets}}] │
│ Pod ENV VARS ← your app reads process.env.* │
│ │
│ GitLab CI/CD Variables (project-level) │
│ └── KUBE_CONFIG → used by kubectl in deploy job │
│ └── CI_REGISTRY_USER / CI_REGISTRY_PASSWORD (auto-set) │
└─────────────────────────────────────────────────────────────┘
Prerequisites — Credentials You Need
These credentials are already stored in the following locations. You do not need to generate new ones for each project.
| Credential | Where it lives | Used for |
|---|---|---|
INFISICAL_CLIENT_ID | ~/.claude/settings.json → mcpServers.infisical.env | Infisical API auth |
INFISICAL_CLIENT_SECRET | same | Infisical API auth |
GITLAB_TOKEN | ~/.claude/settings.json → env.GITLAB_TOKEN | GitLab API |
KUBE_CONFIG (base64) | GitLab project 90 (superpai) CI/CD variable | kubectl access |
The KUBE_CONFIG is shared across all projects. Copy it from superpai — never regenerate it unless the cluster cert changes.
Environment 1 — Windows (Git Bash / Claude Code terminal)
This is the primary environment for Claude Code sessions on the Windows workstation (spl-hp-eb850-bl).
Step 1 — Get an Infisical access token
INFISICAL_TOKEN=$(curl -sk -X POST \
"https://infisical.svcs.anshinhealth.net/api/v1/auth/universal-auth/login" \
-H "Content-Type: application/json" \
-d "{\"clientId\":\"${INFISICAL_CLIENT_ID}\",\"clientSecret\":\"${INFISICAL_CLIENT_SECRET}\"}" \
| python -c "import sys,json; print(json.load(sys.stdin)['accessToken'])")
echo "Token acquired: ${INFISICAL_TOKEN:0:20}..."
Note:
INFISICAL_CLIENT_IDandINFISICAL_CLIENT_SECRETare in your Claude Codesettings.jsonenv block and are available automatically in all Claude Code Bash sessions.
Step 2 — Create the Infisical project
ORG_ID="6ee19673-90fe-4c2c-8e5f-1ebd9a9f7cf1" # Anshin Health org — fixed
NEW_PROJECT=$(curl -sk -X POST \
"https://infisical.svcs.anshinhealth.net/api/v2/workspace" \
-H "Authorization: Bearer $INFISICAL_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"projectName\":\"<your-project-name>\",\"slug\":\"<your-project-slug>\",\"organizationId\":\"${ORG_ID}\"}" \
| python -c "import sys,json; d=json.load(sys.stdin); print(d['project']['id'])")
echo "Project ID: $NEW_PROJECT"
Replace <your-project-name> and <your-project-slug> with your project identifier (e.g. anshin-knowledge / anshin-knowledge).
Step 3 — Add application secrets
For each secret your app needs:
add_secret() {
local KEY=$1
local VALUE=$2
curl -sk -X POST \
"https://infisical.svcs.anshinhealth.net/api/v3/secrets/raw/${KEY}" \
-H "Authorization: Bearer $INFISICAL_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"workspaceId\":\"${NEW_PROJECT}\",\"environment\":\"prod\",\"secretPath\":\"/\",\"secretValue\":\"${VALUE}\"}" \
| python -c "import sys,json; d=json.load(sys.stdin); print(d.get('secret',{}).get('secretKey','ERR'))"
}
# Example: secrets for a Bun/Hono server
add_secret "JWT_SECRET" "$(python -c 'import secrets; print(secrets.token_hex(32))')"
add_secret "NODE_ENV" "production"
add_secret "PORT" "3000"
# Add your app-specific secrets here
add_secret "DATABASE_URL" "your-db-url"
Step 4 — Verify secrets exist
curl -sk \
"https://infisical.svcs.anshinhealth.net/api/v3/secrets/raw?workspaceSlug=<your-project-slug>&environment=prod&secretPath=/" \
-H "Authorization: Bearer $INFISICAL_TOKEN" \
| python -c "import sys,json; [print(s['secretKey']) for s in json.load(sys.stdin).get('secrets',[])]"
Step 5 — Create the GitLab repo (if not done yet)
# Create repo via API
curl -sk -X POST "https://gitlab.anshinhealth.net/api/v4/projects" \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"name\":\"<your-repo-name>\",\"namespace_id\":6,\"initialize_with_readme\":false}" \
| python -c "import sys,json; d=json.load(sys.stdin); print(f'Project ID: {d[\"id\"]} | URL: {d[\"web_url\"]}')"
namespace_id: 6 = the engineering group.
Step 6 — Set KUBE_CONFIG on the new GitLab project
# Grab KUBE_CONFIG from superpai (project 90) and copy to your new project
PROJECT_ID=<your-gitlab-project-id>
KUBE_CONFIG_VAL=$(curl -sk \
"https://gitlab.anshinhealth.net/api/v4/projects/90/variables/KUBE_CONFIG" \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
| python -c "import sys,json; print(json.load(sys.stdin)['value'])")
curl -sk -X POST \
"https://gitlab.anshinhealth.net/api/v4/projects/${PROJECT_ID}/variables" \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"key\":\"KUBE_CONFIG\",\"value\":\"${KUBE_CONFIG_VAL}\",\"protected\":false,\"masked\":false}" \
| python -c "import sys,json; d=json.load(sys.stdin); print('Set: '+d.get('key','ERR: '+str(d)[:80]))"
Step 7 — Configure SSL for internal GitLab
Git Credential Manager doesn't know about your internal CA. Disable TLS verification for just this host:
cd "C:\projects\<your-repo>"
git config http.https://gitlab.anshinhealth.net.sslVerify false
Set the authenticated remote URL using your PAT:
git remote set-url origin "https://bryan.lee:${GITLAB_TOKEN}@gitlab.anshinhealth.net/engineering/<your-repo>.git"
Step 8 — Push and verify pipeline triggers
git push -u origin dev
# Watch pipelines:
# https://gitlab.anshinhealth.net/engineering/<your-repo>/-/pipelines
Environment 2 — WSL2 (Ubuntu)
WSL2 is used for Linux-native tooling (kubectl, helm, ansible). The credentials live in different paths than Windows.
Key path differences
| Item | Windows path | WSL2 path |
|---|---|---|
| Claude settings | C:\Users\BryanLee\.claude\settings.json | /home/bryanlee/.claude/settings.json |
| kubeconfig | C:\Users\BryanLee\.kube\config | /home/bryanlee/.kube/config |
| Projects | C:\projects\ | /home/bryanlee/projects/ |
Accessing Windows files from WSL2
The Git Bash shell on Windows intercepts Linux-style paths. Use this pattern to access WSL2 files from Windows:
# From Windows Git Bash / Claude Code — read a WSL2 file
powershell -Command "wsl -d Ubuntu -- cat /home/bryanlee/.claude/settings.json"
# Copy file from WSL2 to Windows
powershell -Command "wsl -d Ubuntu -- cat /home/bryanlee/path/file" > "C:\destination\file"
Getting credentials in WSL2
Open a WSL2 terminal directly, or run via powershell:
# Extract Infisical credentials from settings.json (WSL2 side)
INFISICAL_CLIENT_ID=$(cat /home/bryanlee/.claude/settings.json | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d['mcpServers']['infisical']['env']['INFISICAL_CLIENT_ID'])")
INFISICAL_CLIENT_SECRET=$(cat /home/bryanlee/.claude/settings.json | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d['mcpServers']['infisical']['env']['INFISICAL_CLIENT_SECRET'])")
GITLAB_TOKEN=$(cat /home/bryanlee/.claude/settings.json | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d['env']['GITLAB_TOKEN'])")
Steps 1–6 — Same as Windows
Run the exact same curl commands from Steps 1–6 above in your WSL2 terminal. The API endpoints are all HTTPS and accessible from both environments.
WSL2-specific: kubectl configuration
In WSL2, kubectl reads from ~/.kube/config. To set it up from the GitLab CI variable:
# Decode and write the kubeconfig
KUBE_CONFIG_VAL=$(curl -sk \
"https://gitlab.anshinhealth.net/api/v4/projects/90/variables/KUBE_CONFIG" \
-H "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")
mkdir -p ~/.kube
echo "$KUBE_CONFIG_VAL" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
# Verify
kubectl get nodes
WSL2-specific: git remote SSL
WSL2's git uses the system CA bundle. Add the internal CA:
# Option A: skip TLS just for this repo (same as Windows)
cd ~/projects/<your-repo>
git config http.https://gitlab.anshinhealth.net.sslVerify false
# Option B: install the internal CA permanently (if you have it)
sudo cp /path/to/anshin-internal-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
Environment 3 — Claude Desktop (macOS / Windows app)
Claude Desktop runs MCP servers but does not have a terminal shell. You cannot run bash commands directly. There are two approaches:
Approach A — Claude Desktop + MCP tools (recommended)
Claude Desktop has access to the Infisical MCP server configured in settings.json. Use MCP tool calls directly in the chat to manage secrets.
Creating a secret via MCP in Claude Desktop:
Ask Claude Desktop:
"Using the Infisical MCP server, create a new project called
my-projectin the Anshin org, then add these secrets to the prod environment: JWT_SECRET=<value>, NODE_ENV=production"
Claude Desktop will call infisical_create_workspace and infisical_create_secret MCP tools without needing a terminal.
Limitations of Claude Desktop:
- Cannot run
git pushorkubectlwithout a connected terminal MCP server - Cannot access the Windows filesystem directly (no
Read/Writetools unless shell MCP is configured) - CI/CD variable setup via GitLab API must be done through the chat using
WebFetch(POST requests)
Approach B — Claude Desktop → delegate to terminal session
From Claude Desktop, generate the exact bash commands and then execute them in a separate Claude Code session or terminal:
- In Claude Desktop: generate all the
curlcommands with correct values filled in - Copy to a Claude Code terminal session (Git Bash on Windows or WSL2)
- Execute there
This is the safest pattern when Claude Desktop is being used for planning but Claude Code handles execution.
Checking Claude Desktop's available MCP servers
Claude Desktop loads MCP servers from its own config file. On Windows:
C:\Users\BryanLee\AppData\Roaming\Claude\claude_desktop_config.json
Ensure infisical is listed there if you want to use MCP tools directly from Desktop.
Full New-Project Checklist
Use this checklist every time you start a new deployable project:
Phase 1 — Repository
- Create GitLab repo in
engineeringgroup (namespace_id: 6) - Initialize with
devbranch (CI/CD trigger) andmainbranch (stable) - Set
git config http.https://gitlab.anshinhealth.net.sslVerify falselocally - Set PAT-authenticated remote:
https://bryan.lee:${GITLAB_TOKEN}@gitlab.anshinhealth.net/...
Phase 2 — Infisical
- Authenticate:
POST /api/v1/auth/universal-auth/login→ saveaccessToken - Create project:
POST /api/v2/workspacewith org ID6ee19673-... - Add
NODE_ENV=production - Add
PORT=<port> - Add all app-specific secrets (DB credentials, JWT secrets, API keys, etc.)
- Verify:
GET /api/v3/secrets/raw?workspaceSlug=<slug>&environment=prod&secretPath=/
Phase 3 — GitLab CI/CD Variables
- Set
KUBE_CONFIG— copy from project 90 (superpai), never regenerate - Confirm
CI_REGISTRY_USER/CI_REGISTRY_PASSWORDare auto-provided (no action needed) - Set any additional pipeline-specific variables (e.g.
HELM_CHART_VERSION)
Phase 4 — K8s Manifests
-
k8s/base/namespace.yaml— create the namespace -
k8s/base/infisical-secret.yaml—InfisicalSecretCR pointing to your Infisical project slug -
k8s/base/deployment.yaml—envFrom: [{secretRef: {name: <project>-secrets}}] -
k8s/base/pvc.yaml— persistent storage if needed (storageClass:nfs) -
k8s/base/service.yaml+ingress.yaml— ClusterIP + Caddy ingress
Phase 5 — Pipeline
-
.gitlab-ci.ymluses Kaniko (not docker:dind) - Runner tags:
orchestrate-build(build),orchestrate-dev(deploy/verify) - Build job: Kaniko executor with
CI_REGISTRYdestination - Deploy job:
kubectl apply+rollout statuswith 3-minute timeout - Verify job: health check loop against the live URL
Phase 6 — Push & Monitor
-
git push origin dev— triggers pipeline - Monitor:
https://gitlab.anshinhealth.net/engineering/<repo>/-/pipelines - After first successful deploy: run
init-adminor equivalent seed script - Add any post-deploy secrets (e.g.
MCP_TOKENafter first admin login) to Infisical
Reference: Fixed Values
These values never change — hardcode them in your scripts:
| Constant | Value |
|---|---|
| Infisical base URL | https://infisical.svcs.anshinhealth.net |
| Infisical org ID | 6ee19673-90fe-4c2c-8e5f-1ebd9a9f7cf1 |
| GitLab base URL | https://gitlab.anshinhealth.net |
| GitLab engineering group ID | 6 |
| Source of KUBE_CONFIG | GitLab project ID 90 (superpai), variable KUBE_CONFIG |
| K8s storage class | nfs (QNAP NFS at 10.10.96.31) |
| Container registry | registry.anshinhealth.net (no port — GitLab integrated) |
| Infisical K8s auth secret | infisical-universal-auth in namespace infisical |
Troubleshooting
"Secret already exists" error from Infisical
# Use PATCH instead of POST to update an existing secret
curl -sk -X PATCH \
"https://infisical.svcs.anshinhealth.net/api/v3/secrets/raw/${KEY}" \
-H "Authorization: Bearer $INFISICAL_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"workspaceId\":\"${WS_ID}\",\"environment\":\"prod\",\"secretPath\":\"/\",\"secretValue\":\"${NEW_VALUE}\"}"
"TLS certificate verification disabled" warning
Expected for internal GitLab. The git config http.https://gitlab.anshinhealth.net.sslVerify false setting is correct — it scopes the bypass to only this host.
Pipeline stuck on "pending"
The runner has a single build slot (resource_group: kaniko-build-1). If another pipeline is running, it queues. Check:
https://gitlab.anshinhealth.net/engineering/<repo>/-/pipelines
Do not push another commit while a pipeline is running — it will queue a second build.
"missing OAuth configuration" from Git Credential Manager
The internal GitLab uses a self-signed cert that GCM can't verify. Solution: always embed the PAT in the remote URL:
git remote set-url origin "https://bryan.lee:${GITLAB_TOKEN}@gitlab.anshinhealth.net/engineering/<repo>.git"
InfisicalSecret CR not syncing
Check the operator logs:
kubectl logs -n infisical deployment/infisical-operator-controller-manager --tail=50
Common cause: the infisical-universal-auth secret in the infisical namespace has expired credentials. Re-run the Infisical login flow and update the secret.
kubectl "connection refused" or "certificate signed by unknown authority"
The KUBE_CONFIG is for the K8s API at https://10.10.97.1:6443. If you're running kubectl from outside the lab network (no VPN), it will fail. Use Claude Code on the Windows workstation which is on the same subnet.