6 KiB
Phase 4: Miniflux Migration to Kubernetes
Goal: Migrate Miniflux entirely off indri and onto k8s, retire brew PostgreSQL, rename k8s-pg to pg
Status: Complete (2026-01-20)
Prerequisites: Phase 3 complete
Overview
This phase completed the miniflux migration and retired brew PostgreSQL:
- Deployed miniflux container in k8s via ArgoCD
- Exposed via Tailscale Ingress at
feed.tail8d86e.ts.net - Removed all miniflux infrastructure from indri (ansible role, brew service, Tailscale serve)
- Retired brew PostgreSQL (no longer needed)
- Renamed k8s-pg to pg (canonical Tailscale hostname)
- Updated borgmatic to backup only
pg.tail8d86e.ts.net - Updated all zk documentation
New Files
| Path | Purpose |
|---|---|
argocd/apps/miniflux.yaml |
ArgoCD Application definition |
argocd/manifests/miniflux/deployment.yaml |
Miniflux Deployment |
argocd/manifests/miniflux/service.yaml |
ClusterIP Service |
argocd/manifests/miniflux/ingress-tailscale.yaml |
Tailscale Ingress for feed.tail8d86e.ts.net |
argocd/manifests/miniflux/secret-db.yaml.tpl |
Database URL secret documentation |
argocd/manifests/miniflux/kustomization.yaml |
Kustomize configuration |
argocd/manifests/miniflux/README.md |
Setup instructions |
Modified Files
| Path | Change |
|---|---|
ansible/playbooks/indri.yml |
Removed miniflux and postgresql roles, simplified pre_tasks |
ansible/roles/tailscale_serve/defaults/main.yml |
Removed svc:feed and svc:pg entries |
ansible/roles/alloy/defaults/main.yml |
Removed miniflux and postgresql logs, disabled postgres metrics |
ansible/roles/borgmatic/defaults/main.yml |
Updated to backup only pg.tail8d86e.ts.net |
ansible/roles/borgmatic/tasks/main.yml |
Added .pgpass file management |
argocd/manifests/databases/service-tailscale.yaml |
Renamed hostname from k8s-pg to pg |
Deleted Files
| Path | Reason |
|---|---|
ansible/roles/miniflux/ |
Entire role no longer needed |
ansible/roles/postgresql/ |
Brew PostgreSQL no longer needed |
Verification
- Miniflux pod healthy in k8s
- https://feed.tail8d86e.ts.net accessible
- User
eblumecan log in - Feeds visible and entries readable
pg.tail8d86e.ts.netresolves to k8s PostgreSQL- Old
k8s-pgandfeeddevices removed from Tailscale - brew miniflux and postgresql services stopped
- Tailscale serve entries cleared from indri
- zk documentation updated
Implementation Notes
Lessons learned and issues encountered
CNPG-Generated Password vs 1Password
Problem: Initial secret template used 1Password for miniflux database password, but CNPG auto-generates the bootstrap owner password.
Solution: Reference the CNPG-generated password from blumeops-pg-app secret:
kubectl create secret generic miniflux-db -n miniflux \
--from-literal=url="$(kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
Table Ownership Issue After P3 Restore
Problem: Miniflux pod crashed with "permission denied for table schema_version".
Root cause: P3 restore was run as the eblume superuser, so all tables were created owned by eblume, not miniflux.
Solution: Transfer ownership of all tables to miniflux:
DO $$
DECLARE r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
EXECUTE 'ALTER TABLE public.' || quote_ident(r.tablename) || ' OWNER TO miniflux';
END LOOP;
END$$;
Tailscale Ingress Hostname Suffix
Behavior: When requesting a Tailscale hostname that's already taken, the operator adds a suffix (e.g., feed-1).
Workflow:
- Deploy initially - gets
feed-1.tail8d86e.ts.net - Clear old
svc:feedfrom indri - Delete old
feeddevice from Tailscale admin - Delete and recreate the Ingress - now claims
feed
Renaming Tailscale Service Hostname
Problem: Changing the tailscale.com/hostname annotation doesn't automatically update the Tailscale device.
Solution: Delete the service and let ArgoCD recreate it:
kubectl -n databases delete service blumeops-pg-tailscale
argocd app sync blumeops-pg
.pgpass Management Migration
Issue: The postgresql role managed ~/.pgpass for borgmatic. With postgresql role deleted, borgmatic couldn't authenticate.
Solution: Moved .pgpass management to the borgmatic role. Password is still fetched in playbook pre_tasks as borgmatic_db_password.
Ansible Check Mode and Registered Variables
Problem: Running provision-indri --check --diff failed in the podman role with "Conditional result (True) was derived from value of type 'str'" errors.
Root cause: Command tasks are skipped in check mode, leaving registered variables undefined or with unexpected types when used in conditionals.
Solution: Added check_mode: false to read-only command tasks that gather information:
- name: Check if podman machine exists
ansible.builtin.command:
cmd: podman machine list --format json
register: podman_machine_list
changed_when: false
check_mode: false # Safe to run in check mode - read-only
Lesson: Any task that registers a variable used in conditionals should have check_mode: false if the command is read-only/safe.
1Password CLI on Headless Hosts
Issue: Attempted to run op commands on indri, but 1Password CLI requires interactive authentication (biometrics/password).
Solution: All op commands must be in pre_tasks of the playbook with delegate_to: localhost so they run on gilbert (the workstation with GUI auth).
Git Workflow for Phase 4
- Created feature branch:
feature/p4-miniflux - Made incremental commits throughout implementation
- Pointed
minifluxandblumeops-pgapps to feature branch for testing - Created PR #33 for review
- After merge, reset apps to main:
argocd app set miniflux --revision main argocd app set blumeops-pg --revision main argocd app sync apps