1Password Connect migration plan (ESO)
This document outlines the migration from SOPS-managed application secrets to 1Password Connect with External Secrets Operator (ESO).
Principles and constraints
- Keep a minimal SOPS-encrypted bootstrap for cluster bring-up and Flux decryption.
- Use ESO with a shared
ClusterSecretStorenamedonepassword. - Deploy ESO and 1Password Connect in
external-secretsnamespace. - Use a push-style migration: load secrets into 1Password first, then switch workloads to
ExternalSecret. - Organize cutovers namespace-by-namespace to reduce coordination overhead.
Phase 0: Discovery and design
- Inventory current SOPS secrets by namespace and classify shared vs app-specific values.
- Confirm 1Password vault strategy (default vault:
homelab). - Confirm naming conventions and field mapping.
- Prepare migration inputs: SOPS path -> 1Password item title/field labels.
Phase 0 outputs (current repository state)
| Namespace | App | SOPS file path | Notes |
|---|---|---|---|
| bootstrap | bootstrap | kubernetes/bootstrap/age-key.sops.yaml |
Shared bootstrap (age key). |
| bootstrap | bootstrap | kubernetes/bootstrap/github-deploy-key.sops.yaml |
Shared bootstrap (Flux deploy key). |
| cert-manager | cert-manager | kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml |
App-specific issuer credentials. |
| database | cloudnative-pg | kubernetes/apps/database/cloudnative-pg/cluster/secrets.sops.yaml |
App-specific database credentials. |
| database | ext-postgres-operator | kubernetes/apps/database/ext-postgres-operator/app/secret.sops.yaml |
Operator credentials. |
| database | pgadmin | kubernetes/apps/database/pgadmin/app/secret.sops.yaml |
App-specific credentials. |
| datasci | cloudnative-pg | kubernetes/apps/datasci/postgres/cloudnative-pg/cluster/secrets.sops.yaml |
App-specific database credentials. |
| datasci | ext-postgres-operator | kubernetes/apps/datasci/postgres/ext-postgres-operator/app/secret.sops.yaml |
Operator credentials. |
| datasci | mlflow | kubernetes/apps/datasci/mlflow/app/secret.sops.yaml |
App-specific credentials. |
| datasci | mlflow | kubernetes/apps/datasci/mlflow/app/secret-gateway.sops.yaml |
Gateway-specific credentials. |
| datasci | mlflow | kubernetes/apps/datasci/mlflow/app/secret-s3.sops.yaml |
Object storage credentials. |
| default | calibre-web-automated | kubernetes/apps/default/calibre-web-automated/app/secret.sops.yaml |
App-specific credentials. |
| default | calibre-web-automated | kubernetes/apps/default/calibre-web-automated/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | homebox | kubernetes/apps/default/homebox/app/secret.sops.yaml |
App-specific credentials. |
| default | homepage | kubernetes/apps/default/homepage/app/secret.sops.yaml |
App-specific credentials. |
| default | jelu | kubernetes/apps/default/jelu/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | karakeep | kubernetes/apps/default/karakeep/app/secret.sops.yaml |
App-specific credentials. |
| default | karakeep | kubernetes/apps/default/karakeep/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | mealie | kubernetes/apps/default/mealie/app/secret.sops.yaml |
App-specific credentials. |
| default | mealie | kubernetes/apps/default/mealie/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | memos | kubernetes/apps/default/memos/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | miniflux | kubernetes/apps/default/miniflux/app/secret.sops.yaml |
App-specific credentials. |
| default | miniflux | kubernetes/apps/default/miniflux/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| default | picoshare | kubernetes/apps/default/picoshare/app/secret.sops.yaml |
App-specific credentials. |
| debug | whoami | kubernetes/apps/debug/whoami/app/secret-oidc.sops.yaml |
OIDC client credentials. |
| flux | flux vars | kubernetes/flux/vars/cluster-secrets.sops.yaml |
Shared bootstrap variables. |
| flux | flux vars | kubernetes/flux/vars/custom-secrets.sops.yaml |
Shared customization variables. |
| flux-system | flux-system | kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml |
GitHub webhook secret. |
| network | cloudflared | kubernetes/apps/network/cloudflared/app/secret.sops.yaml |
Tunnel credentials. |
| network | external-dns | kubernetes/apps/network/external-dns/app/secret.sops.yaml |
DNS provider credentials. |
| observability | grafana operator | kubernetes/apps/observability/grafana/operator/secret.sops.yaml |
App-specific credentials. |
| observability | grafana operator | kubernetes/apps/observability/grafana/operator/secret-oidc.sops.yaml |
OIDC client credentials. |
| observability | kube-prometheus-stack | kubernetes/apps/observability/kube-prometheus-stack/app/secret-additionalScrapeConfigs.sops.yaml |
Additional scrape configs. |
| observability | kube-prometheus-stack | kubernetes/apps/observability/kube-prometheus-stack/app/secret-alertmgrNotifiers.sops.yaml |
Alertmanager notifiers. |
| observability | nut-exporter | kubernetes/apps/observability/nut-exporter/app/secret.sops.yaml |
App-specific credentials. |
| security | authelia | kubernetes/apps/security/authelia/app/secret.sops.yaml |
App-specific credentials. |
| security | lldap | kubernetes/apps/security/lldap/app/secret.sops.yaml |
Directory credentials. |
| blog | hugo | kubernetes/apps/blog/hugo/app/secret.sops.yaml |
App-specific credentials. |
Notes:
- Bootstrap SOPS files remain encrypted after cutover for bootstrap/Flux workflows.
- Namespace inventories should drive
ExternalSecretmappings during Phase 3.
Phase 1: GitOps foundation in kubernetes/apps/external-secrets
Status checklist:
Phase 1 outputs (current repository state)
- Namespace + app root kustomization:
kubernetes/apps/external-secrets/. - ESO app:
kubernetes/apps/external-secrets/external-secrets-operator/. - 1Password app:
kubernetes/apps/external-secrets/onepassword/. ClusterSecretStore:onepassword.- Bootstrap secret expected in
external-secretsnamespace:onepassword-secretwith keys:1password-credentials.jsontoken
Phase 2: Migration tooling and documentation
Phase 3: Namespace-by-namespace cutover
-
- Create/adjust
ExternalSecretresources mapping K8s keys to 1Password fields. - Update namespace app manifests to consume ESO-managed secrets.
- Validate reconciliation (
ExternalSecret/ClusterSecretStoreReady conditions). - Remove direct SOPS consumption once cutover is verified.
- Create/adjust
Phase 4: Cleanup and legacy handling
Additional recommended milestones
- RBAC hardening: minimize ESO permissions and limit
ClusterExternalSecretuse where possible. - Reliability: set/verify resource requests and disruption budgets for ESO + Connect.
- Observability: add dashboards/alerts for ESO reconciliation failures.
- Resilience drills: practice 1Password credential rotation and ESO recovery.
Naming conventions (1Password + ESO)
- 1Password vault:
homelab(use consistently for ESO andopreferences). opreference pattern:op://homelab/<namespace>.<appname>/[section]/<field>.- 1Password item title:
{namespace}.{appname}(example:default.ghost). - Use
sectionfor grouping by purpose (for exampleapp,oidc,db) when needed. - Separator preference between namespace and app name:
., then_, then-. - ESOβs 1Password provider matches by field label and ignores section names, so field labels must be unique per item.
- Shared item titles:
shared.{purpose}. - Secret fields:
username,password,apiKey,token,certificate,privateKey. - Kubernetes Secret names:
apporapp-<purpose>in the app namespace. - ExternalSecret resource names:
{namespace}-{app}-{source}(example:default-ghost). - ClusterSecretStore name:
onepassword. remoteRef.keymaps to item title;remoteRef.propertymaps to field label.