How flow Is Actually Used
This is the part worth writing down.
The repo is a flow workspace. All of its automation lives in one .execs/ directory, split by
concern rather than by application: utilities, cluster operations, apps, infrastructure,
networking, storage, platform, labs, and aggregates. It’s a couple thousand lines of flow YAML,
which sounds like a lot until you consider it replaced a pile of shell scripts and a much larger
pile of things I used to keep in my head.
Everything composes from a shared library
The single most useful thing I did was pull the repeated parts into a utils namespace and
then never write them again. A deploy is a serial composition of references:
- verb: deploy
name: jellyfin
tags: [media]
description: Deploy the Jellyfin media server to Kubernetes using homelab chart
serial:
failFast: true
dir: //apps/media/jellyfin
execs:
- ref: install utils:helm-base
args: [jellyfin, jellyfin]
- ref: verify utils:deployment
args: [jellyfin, jellyfin]
Every app looks like that. Adding a new one is a values file, a chart reference, and about six
lines of flow. The namespace creation, the repo add, the helm upgrade --install, the rollout
wait, the verification. All of it lives in utils and is written once.
Secrets come from the vault, not the repo
Credentials are declared as parameters and resolved at run time. Nothing is committed, and nothing sits in my shell history:
params:
- secretRef: bring-username
envKey: BRING_USERNAME
- envKey: ADDITIONAL_ARGS
text: |
--set controllers.main.containers.bring-api.env.BRING_USERNAME=$BRING_USERNAME
A shared create utils:secret executable turns those into Kubernetes secrets during the deploy.
There’s no sealed-secrets controller, no SOPS, no external secrets operator. For a single
operator, flow’s vault is the whole secret management story.
Aggregates for the things I do together
I rarely want to deploy one media app. flow deploy media-stack runs the six of them in order
and then prints the URLs; flow verify media-stack checks all six in parallel. The aggregate is
just another executable that references the others.
Operations, not just deploys
The half of the workspace I didn’t expect to write is the diagnostic half: check health,
check issues, show overview, show inventory, debug pod, debug service, and
export diagnostics. These exist because at 11pm I do not want to remember the right
kubectl get incantation across four namespaces. flow check health tells me whether anything
is wrong, and that’s the whole point.
There’s also a small labs namespace with httpbin and netshoot behind test dns / test http
/ exec tcpdump executables, for when something is broken in a way that needs poking at from
inside the cluster.
Templates for new services
New apps get scaffolded from flow templates with a form that asks for the app name, namespace, chart repo, and whether it needs ingress, secrets, or monitoring. It emits the values file and the executable stubs. It’s the difference between adding a service being a ten-minute job and a this-weekend job.