Skip to content

# Secrets Manager — Infisical on CT 109

Status: deployed 2026-05-22; migrated CT 112 → CT 109 on 2026-05-26. Running at `http://192.168.1.8:8200` (`http://secrets.nuclide.lan:8200`). LAN-only, no Zoraxy route — secrets must not be internet-exposed.

Stack: `infisical/infisical:latest-postgres` + Postgres 16 + Redis 7, all on CT 109 (`ops`, `192.168.1.8`). Compose at `/opt/stacks/infisical/` on CT 109. CT 112 ("secrets") is decommissioned; LXC pending removal.

## Problem statement

Secrets are currently scattered across:

Location | Count | Risk
--- | --- | ---
`/opt/stacks/ai/.env` on CT 104 | ~55 keys | Not versioned; duplicated across stacks
Per-stack `.env` files CT 104 | ~30 keys | Several duplicated (WALG keys, IMMICH_API_KEY, etc.)
CT 101, 111, 113 `.env` files | ~30 keys | No central rotation story
Coder `main.tf` (hardcoded env vars) | 6 | Committed to Gitea; visible in template history
HA `secrets.yaml` on HAOS VM 100 | unknown | Not backed up centrally

Goal: single LAN-only secret store that agents, Docker services, and Coder workspaces pull from programmatically. ~85 unique secrets identified in sweep (2026-05-22).

## Candidate solutions

### 1. Infisical (recommended)

Open-source HashiCorp Vault alternative. Docker-compose deployable. Native integrations for:

- Docker secrets injection (Infisical agent sidecar)
- Kubernetes/Coder workspace env injection
- REST API + OIDC machine identities
- OIDC SSO (can federate with Pocket-ID)
- Web UI

Deployment: separate LXC (recommended — see rationale below). Postgres backend → candidate for CT 113 consolidation once second NVMe lands.

Port: 8080 internally; no external exposure needed (LAN-only MCP sidecar pattern).

### 2. HashiCorp Vault (OSS)

Industry standard. Steeper ops overhead (unsealing, audit logs, lease renewal). Overkill for a homelab unless you need HSM-grade guarantees.

### 3. Doppler (SaaS)

Managed; free tier; native CLI and Docker integration. Outbound dependency; secrets leave the homelab. Not suitable given the DLR/LUMEN data handling doctrine.

### 4. Stay with Vaultwarden + manual OIDC

Already deployed. Works for human access. No programmatic injection without writing custom code. Dead end for agent/pipeline automation.

## Recommendation: Infisical on a dedicated LXC

### Why a separate LXC?

1. Blast radius isolation — if the CT 104 Docker stack is compromised, the secret store is not on the same attack surface.
2. Minimal footprint — Infisical + Postgres is the only workload; nothing else can mess with the process space.
3. Simpler audit — outbound connections from CT 104 are numerous; a dedicated secrets LXC should have near-zero egress.
4. HA restart independence — CT 104 restarts (image gen, GPU passthrough experiments) don't affect secret availability.

Suggested: CT 112 (next available), 2 vCPU / 2 GB RAM, 8 GB disk on local-zfs.

## Architecture

    flowchart TD
      subgraph CT112["CT 112 — secrets"]
        infisical["Infisical Server\n:8080"]
        pg_sec["Postgres\n(infisical DB)"]
        infisical --- pg_sec
      end

      subgraph CT104["CT 104 — Docker host"]
        agent["Infisical Agent\n(sidecar per stack)"]
        env_file[".env (templated)\nrendered at startup"]
        agent -->|pull on start| infisical
        agent --> env_file
      end

      subgraph CT111["CT 111 — Coder"]
        coder["Coder server"]
        ws["Workspace containers\n(env injected at provision)"]
        coder -->|agent token| infisical
        coder --> ws
      end

      subgraph CT110["CT 110 — Pocket-ID"]
        oidc["OIDC IdP"]
      end

      oidc -->|machine identity| infisical
      oidc -->|user SSO| infisical

## Setup plan

### Phase 1 — Deploy Infisical on CT 112

    # On Proxmox host
    pvesh create /nodes/nuc/lxc \
      --ostemplate local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
      --vmid 112 --hostname secrets --memory 2048 --cores 2 \
      --rootfs local-zfs:8 --net0 name=eth0,bridge=vmbr0,ip=dhcp

    # Inside CT 112
    apt-get install -y docker.io docker-compose-plugin
    # Deploy from https://github.com/Infisical/infisical (official compose)

Infisical requires a Redis instance alongside Postgres. The official `docker-compose.yml` bundles both.

### Phase 2 — Migrate CT 104 secrets

1. Create an Infisical project `homelab/ct104`
2. Import existing `.env` keys via Infisical CLI: `infisical import --env prod --path /homelab/ct104 < .env`
3. Add Infisical agent to each compose stack as a sidecar that renders `.env` from templates at container start

### Phase 3 — Coder workspace injection

Infisical has a native Coder integration (via machine identity token). Replace hardcoded env vars in `main.tf` with:

    data "external" "secrets" {
      program = ["infisical", "export", "--env", "prod", "--path", "/coder/workspaces", "--format", "dotenv-export"]
    }

Or use the Infisical agent installed in the workspace image.

### Phase 4 — OIDC SSO via Pocket-ID

Infisical supports OIDC SSO under Settings → Authentication → OIDC. Register a new client in Pocket-ID with callback `https://secrets.nuclide.systems/api/v1/sso/oidc/callback` (or LAN-only URL).

## Prioritisation

Item | Effort | Value
--- | --- | ---
Create CT 112, deploy Infisical | 1h | High — removes .env sprawl
Migrate CT 104 .env | 30min | High
Coder template injection | 1h | Medium — workspaces currently work
HA secrets.yaml → Infisical | 30min (HA add-on) | Medium
OIDC SSO via Pocket-ID | 30min | Low (convenience)

## Short-term mitigation (before CT 112 is built)

1. Remove secrets from Coder `main.tf` that appear in Gitea history — use Coder template variables or env injection instead.
2. Keep `/opt/stacks/ai/.env` mode `0600`; ensure it is in `.gitignore` for any repo that mounts that directory.
3. Use Vaultwarden as the authoritative human copy; rotate any key that has appeared in Claude Code context (see `audit-claude-code-meta.md`).

## Related

- [[postgres-consolidation]] — CT 113 will consolidate Postgres instances including Infisical's DB
- [[feedback_zoraxy_confirm]] — Zoraxy route for `secrets.nuclide.systems` will need operator approval when CT 112 is ready
- `security/audit-claude-code-meta.md` — lists secrets that transited Claude API this session